@unhook/cli
Version:
A CLI tool for testing webhooks locally with team collaboration features.
159 lines (142 loc) • 4.8 kB
JavaScript
const os = require('node:os');
const fs = require('node:fs');
const path = require('node:path');
const { spawnSync } = require('node:child_process');
// Get version from package.json
const packageJsonPath = path.join(__dirname, '..', 'package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
const version = packageJson.version;
const cliName = 'unhook';
const platformMap = { win32: 'win32', darwin: 'darwin', linux: 'linux' };
const archMap = { x64: 'x64', arm64: 'arm64' };
const platform = platformMap[os.platform()];
const arch = archMap[os.arch()];
const ext = os.platform() === 'win32' ? '.exe' : '';
if (!platform || !arch) {
console.error(
`❌ Unsupported platform or architecture: ${os.platform()}-${os.arch()}`,
);
console.error(
'Supported platforms: Windows (x64), macOS (x64, arm64), Linux (x64, arm64)',
);
process.exit(1);
}
// For Linux, check if we should use musl variant
let targetArch = arch;
if (platform === 'linux') {
try {
// Check for musl by looking for the musl loader
if (
fs.existsSync('/lib/ld-musl-x86_64.so.1') ||
fs.existsSync('/lib/ld-musl-aarch64.so.1')
) {
targetArch = `${arch}-musl`;
}
} catch (_err) {
// If we can't determine, default to glibc variant
}
}
const binName = `${cliName}-${platform}-${targetArch}${ext}`;
const installDir = path.join(os.homedir(), `.${cliName}/bin`);
const versionedInstallDir = path.join(installDir, `v${version}`);
const binPath = path.join(versionedInstallDir, binName);
function showInstallInstructions() {
console.error('💡 To install the CLI binary, run:');
console.error(` npm install ${packageJson.name}`);
console.error(
' or try running this command again - the binary will be downloaded automatically.',
);
}
function runBinary() {
if (!fs.existsSync(binPath)) {
console.error(`❌ Binary not found at ${binPath}`);
console.error(`Expected binary: ${binName}`);
showInstallInstructions();
// Try to run the install script automatically
const attempt = Number.parseInt(
process.env.UNHOOK_CLI_DOWNLOAD_ATTEMPT || '0',
10,
);
if (attempt >= 2) {
console.error(
'❌ Auto-download failed after multiple attempts. Please install manually.',
);
showInstallInstructions();
process.exit(1);
}
console.log('🔄 Attempting to download binary automatically...');
try {
const installScript = path.join(
__dirname,
'..',
'scripts',
'install.cjs',
);
if (fs.existsSync(installScript)) {
// Run the install script synchronously, only install, do not execute binary
const env = {
...process.env,
UNHOOK_CLI_INSTALL_ONLY: '1',
UNHOOK_CLI_DOWNLOAD_ATTEMPT: String(attempt + 1),
};
const installResult = spawnSync(process.execPath, [installScript], {
stdio: 'inherit',
env,
});
if (installResult.status === 0) {
// After installing, try running the binary again
runBinary();
return;
}
console.error(
`❌ Auto-download failed with exit code: ${installResult.status}`,
);
showInstallInstructions();
process.exit(1);
}
} catch (err) {
console.error(`❌ Auto-download failed: ${err.message}`);
showInstallInstructions();
process.exit(1);
}
process.exit(1);
}
try {
// Verify the binary is executable
fs.accessSync(binPath, fs.constants.X_OK);
} catch (err) {
console.error(`❌ Binary is not executable: ${err.message}`);
try {
fs.chmodSync(binPath, 0o755);
console.log('✅ Fixed binary permissions');
} catch (chmodErr) {
console.error(`❌ Failed to fix permissions: ${chmodErr.message}`);
process.exit(1);
}
}
// Execute the binary with all arguments
const result = spawnSync(binPath, process.argv.slice(2), {
stdio: 'inherit',
env: process.env,
});
if (result.error) {
console.error(`❌ Failed to execute binary: ${result.error.message}`);
if (result.error.code === 'ENOENT') {
console.error('Binary not found or not executable');
showInstallInstructions();
} else if (result.error.code === 'EACCES') {
console.error('Permission denied when executing binary');
if (os.platform() === 'darwin') {
console.error('This may be due to macOS security restrictions.');
console.error(
'Try: System Preferences > Security & Privacy > Allow downloaded binary',
);
}
}
process.exit(1);
}
// Exit with the same code as the binary
process.exit(result.status || 0);
}
runBinary();