mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
fix(cli): report binary startup failures with actionable diagnostics (#220)
This commit is contained in:
+83
-27
@@ -5,44 +5,100 @@ const path = require('path');
|
||||
|
||||
const platform = process.platform;
|
||||
const arch = process.arch;
|
||||
const pkgName = `@twsxtd/hapi-${platform}-${arch}`;
|
||||
|
||||
function getBinaryPath() {
|
||||
function getBinaryPath(platformName = platform, archName = arch) {
|
||||
const pkgName = `@twsxtd/hapi-${platformName}-${archName}`;
|
||||
|
||||
try {
|
||||
// Try to find the platform-specific package
|
||||
const pkgPath = require.resolve(`${pkgName}/package.json`);
|
||||
const binName = platform === 'win32' ? 'hapi.exe' : 'hapi';
|
||||
const binName = platformName === 'win32' ? 'hapi.exe' : 'hapi';
|
||||
return path.join(path.dirname(pkgPath), 'bin', binName);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const binPath = getBinaryPath();
|
||||
|
||||
if (!binPath) {
|
||||
console.error(`Unsupported platform: ${platform}-${arch}`);
|
||||
console.error('');
|
||||
console.error('Supported platforms:');
|
||||
console.error(' - darwin-arm64 (macOS Apple Silicon)');
|
||||
console.error(' - darwin-x64 (macOS Intel)');
|
||||
console.error(' - linux-arm64');
|
||||
console.error(' - linux-x64');
|
||||
console.error(' - win32-x64');
|
||||
console.error('');
|
||||
console.error('You can download the binary manually from:');
|
||||
console.error(' https://github.com/tiann/hapi/releases');
|
||||
process.exit(1);
|
||||
function formatCommand(binPath, args) {
|
||||
return [binPath, ...args].map((arg) => JSON.stringify(String(arg))).join(' ');
|
||||
}
|
||||
|
||||
try {
|
||||
execFileSync(binPath, process.argv.slice(2), { stdio: 'inherit' });
|
||||
} catch (e) {
|
||||
// If the binary execution fails, exit with the same code
|
||||
if (e.status !== undefined) {
|
||||
process.exit(e.status);
|
||||
function normalizeExecError(error) {
|
||||
return {
|
||||
status: typeof error?.status === 'number' ? error.status : null,
|
||||
signal: typeof error?.signal === 'string' ? error.signal : null,
|
||||
message: error?.message ? String(error.message) : null,
|
||||
};
|
||||
}
|
||||
|
||||
function reportExecutionFailure(error, binPath, args, log = console.error) {
|
||||
const { status, signal, message } = normalizeExecError(error);
|
||||
|
||||
log(`Failed to execute: ${formatCommand(binPath, args)}`);
|
||||
|
||||
if (signal) {
|
||||
log(`Binary terminated by signal ${signal}.`);
|
||||
}
|
||||
// For other errors (e.g., binary not found), print and exit
|
||||
console.error(`Failed to execute ${binPath}:`, e.message);
|
||||
process.exit(1);
|
||||
|
||||
if (status !== null) {
|
||||
log(`Binary exited with status ${status}.`);
|
||||
}
|
||||
|
||||
if (message) {
|
||||
log(message);
|
||||
}
|
||||
|
||||
return { status, signal };
|
||||
}
|
||||
|
||||
function main() {
|
||||
const binPath = getBinaryPath();
|
||||
|
||||
if (!binPath) {
|
||||
console.error(`Unsupported platform: ${platform}-${arch}`);
|
||||
console.error('');
|
||||
console.error('Supported platforms:');
|
||||
console.error(' - darwin-arm64 (macOS Apple Silicon)');
|
||||
console.error(' - darwin-x64 (macOS Intel)');
|
||||
console.error(' - linux-arm64');
|
||||
console.error(' - linux-x64');
|
||||
console.error(' - win32-x64');
|
||||
console.error('');
|
||||
console.error('You can download the binary manually from:');
|
||||
console.error(' https://github.com/tiann/hapi/releases');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
try {
|
||||
execFileSync(binPath, args, { stdio: 'inherit' });
|
||||
} catch (error) {
|
||||
const { status, signal } = reportExecutionFailure(error, binPath, args);
|
||||
|
||||
if (status !== null) {
|
||||
process.exit(status);
|
||||
}
|
||||
|
||||
if (signal) {
|
||||
try {
|
||||
process.kill(process.pid, signal);
|
||||
} catch {
|
||||
// ignore unsupported/invalid signal names on this platform
|
||||
}
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
formatCommand,
|
||||
getBinaryPath,
|
||||
normalizeExecError,
|
||||
reportExecutionFailure,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { createRequire } from 'node:module';
|
||||
import path from 'node:path';
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const binModulePath = path.resolve(process.cwd(), 'bin/hapi.cjs');
|
||||
const { formatCommand, normalizeExecError, reportExecutionFailure } = require(binModulePath);
|
||||
|
||||
describe('hapi binary launcher error reporting', () => {
|
||||
it('formats command with shell-safe JSON quoting', () => {
|
||||
const command = formatCommand('/tmp/hapi', ['serve', '--name', 'my agent']);
|
||||
expect(command).toBe('"/tmp/hapi" "serve" "--name" "my agent"');
|
||||
});
|
||||
|
||||
it('normalizes child process execution errors', () => {
|
||||
const normalized = normalizeExecError({
|
||||
status: 132,
|
||||
signal: 'SIGILL',
|
||||
message: 'Command failed: /tmp/hapi',
|
||||
});
|
||||
|
||||
expect(normalized).toEqual({
|
||||
status: 132,
|
||||
signal: 'SIGILL',
|
||||
message: 'Command failed: /tmp/hapi',
|
||||
});
|
||||
});
|
||||
|
||||
it('reports execution details before exit handling', () => {
|
||||
const lines: string[] = [];
|
||||
const log = (line: string) => lines.push(line);
|
||||
|
||||
const result = reportExecutionFailure(
|
||||
{
|
||||
status: 132,
|
||||
signal: 'SIGILL',
|
||||
message: 'Illegal instruction (core dumped)',
|
||||
},
|
||||
'/tmp/hapi',
|
||||
['serve', '--port', '3000'],
|
||||
log,
|
||||
);
|
||||
|
||||
expect(result).toEqual({ status: 132, signal: 'SIGILL' });
|
||||
expect(lines).toEqual([
|
||||
'Failed to execute: "/tmp/hapi" "serve" "--port" "3000"',
|
||||
'Binary terminated by signal SIGILL.',
|
||||
'Binary exited with status 132.',
|
||||
'Illegal instruction (core dumped)',
|
||||
]);
|
||||
});
|
||||
|
||||
it('handles unknown failures with generic output', () => {
|
||||
const lines: string[] = [];
|
||||
|
||||
const result = reportExecutionFailure({}, '/tmp/hapi', [], (line: string) => {
|
||||
lines.push(line);
|
||||
});
|
||||
|
||||
expect(result).toEqual({ status: null, signal: null });
|
||||
expect(lines).toEqual(['Failed to execute: "/tmp/hapi"']);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user