Files
hapi/cli/bin/hapi.cjs
T
weishu 47aad06866 fix(release): upgrade Bun and reject mismatched platform binaries
Pin the build toolchain to Bun 1.4.4 so CI includes the darwin-arm64 signing fix required by macOS 27.

Reject stale optional platform packages before launching, document clean reinstall recovery, and add launcher regression coverage.
2026-10-29 09:45:46 +08:00

191 lines
5.5 KiB
JavaScript
Executable File

#!/usr/bin/env node
const { execFileSync } = require('child_process');
const path = require('path');
const { version: PACKAGE_VERSION } = require('../package.json');
const platform = process.platform;
const arch = process.arch;
const RELEASE_URL = 'https://github.com/tiann/hapi/releases';
const OFFICIAL_NPM_REGISTRY = 'https://registry.npmjs.org';
const SUPPORTED_PLATFORMS = [
{
key: 'darwin-arm64',
label: 'darwin-arm64 (macOS Apple Silicon)',
},
{
key: 'darwin-x64',
label: 'darwin-x64 (macOS Intel)',
},
{
key: 'linux-arm64',
label: 'linux-arm64',
},
{
key: 'linux-x64',
label: 'linux-x64',
},
{
key: 'win32-x64',
label: 'win32-x64',
},
];
function getPlatformKey(platformName = platform, archName = arch) {
return `${platformName}-${archName}`;
}
function isSupportedPlatform(platformName = platform, archName = arch) {
return SUPPORTED_PLATFORMS.some((item) => item.key === getPlatformKey(platformName, archName));
}
function getBinaryPath(platformName = platform, archName = arch) {
const pkgName = `@twsxtd/hapi-${platformName}-${archName}`;
let pkgPath;
try {
// Try to find the platform-specific package
pkgPath = require.resolve(`${pkgName}/package.json`);
} catch (e) {
return null;
}
// A failed optional dependency upgrade can leave an older package on disk.
// Node resolves that package even though it does not satisfy our pinned version.
const installedVersion = require(pkgPath).version;
if (installedVersion !== PACKAGE_VERSION) {
throw new Error(
`Platform package version mismatch: ${pkgName}\n` +
`Expected ${PACKAGE_VERSION}, but found ${installedVersion ?? 'unknown'}.\n` +
'Refusing to run a platform binary from a different HAPI release.'
);
}
const binName = platformName === 'win32' ? 'hapi.exe' : 'hapi';
return path.join(path.dirname(pkgPath), 'bin', binName);
}
function formatCommand(binPath, args) {
return [binPath, ...args].map((arg) => JSON.stringify(String(arg))).join(' ');
}
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}.`);
}
if (status !== null) {
log(`Binary exited with status ${status}.`);
}
if (message) {
log(message);
}
return { status, signal };
}
function reportUnsupportedPlatform(platformName = platform, archName = arch, log = console.error) {
log(`Unsupported platform: ${platformName}-${archName}`);
log('');
log('Supported platforms:');
for (const item of SUPPORTED_PLATFORMS) {
log(` - ${item.label}`);
}
log('');
log('You can download the binary manually from:');
log(` ${RELEASE_URL}`);
}
function reportReinstall(log = console.error) {
log('Try reinstalling with the official npm registry:');
log('Use the same package manager as your current installation:');
log(` npm install -g @twsxtd/hapi@${PACKAGE_VERSION} --registry=${OFFICIAL_NPM_REGISTRY}`);
log('For Bun, remove the launcher first to retry skipped optional dependencies:');
log(` bun remove -g @twsxtd/hapi && bun add -g @twsxtd/hapi@${PACKAGE_VERSION} --registry=${OFFICIAL_NPM_REGISTRY}`);
log('');
log('Or download the binary manually from:');
log(` ${RELEASE_URL}`);
}
function reportMissingPlatformPackage(platformName = platform, archName = arch, log = console.error) {
const platformPackage = `@twsxtd/hapi-${platformName}-${archName}`;
log(`Missing platform package: ${platformPackage}`);
log('');
log(`Detected platform ${platformName}-${archName} is supported, but the platform binary package was not installed.`);
log('This may happen when using a registry mirror that has not synced all optionalDependencies.');
log('');
reportReinstall(log);
}
function main() {
if (!isSupportedPlatform()) {
reportUnsupportedPlatform();
process.exit(1);
}
let binPath;
try {
binPath = getBinaryPath();
} catch (error) {
console.error(error.message);
console.error('');
reportReinstall();
process.exit(1);
}
if (!binPath) {
reportMissingPlatformPackage();
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,
getPlatformKey,
getBinaryPath,
isSupportedPlatform,
normalizeExecError,
reportExecutionFailure,
reportMissingPlatformPackage,
reportUnsupportedPlatform,
};