mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
254 lines
7.3 KiB
JavaScript
254 lines
7.3 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Build script for creating standalone Safe Chain binaries
|
|
* Uses esbuild to bundle ES modules, then @yao-pkg/pkg to create executable
|
|
*/
|
|
|
|
import { exec } from 'node:child_process';
|
|
import { promisify } from 'node:util';
|
|
import { existsSync, mkdirSync, copyFileSync, writeFileSync, rmSync, readFileSync, readdirSync } from 'node:fs';
|
|
import { join, dirname } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import * as esbuild from 'esbuild';
|
|
|
|
const execAsync = promisify(exec);
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const ROOT_DIR = join(__dirname, '..');
|
|
const DIST_DIR = join(__dirname, 'dist');
|
|
const BUNDLE_DIR = join(DIST_DIR, 'bundle');
|
|
const SAFE_CHAIN_DIR = join(ROOT_DIR, 'packages/safe-chain');
|
|
|
|
/**
|
|
* Parse command line arguments
|
|
*/
|
|
function parseArgs() {
|
|
const args = process.argv.slice(2);
|
|
const platform = args.find(arg => arg.startsWith('--platform='))?.split('=')[1] || 'macos';
|
|
return { platform };
|
|
}
|
|
|
|
/**
|
|
* Ensure dist directory exists
|
|
*/
|
|
function ensureDistDirectory() {
|
|
if (!existsSync(DIST_DIR)) {
|
|
mkdirSync(DIST_DIR, { recursive: true });
|
|
}
|
|
if (!existsSync(BUNDLE_DIR)) {
|
|
mkdirSync(BUNDLE_DIR, { recursive: true });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Bundle ES modules using esbuild
|
|
* This converts ES modules to CommonJS that pkg can handle
|
|
*/
|
|
async function bundleWithEsbuild() {
|
|
console.log('Bundling with esbuild...');
|
|
|
|
const entryPoint = join(SAFE_CHAIN_DIR, 'bin/safe-chain.js');
|
|
const outputFile = join(BUNDLE_DIR, 'safe-chain-bundled.cjs');
|
|
|
|
await esbuild.build({
|
|
entryPoints: [entryPoint],
|
|
bundle: true,
|
|
platform: 'node',
|
|
target: 'node20',
|
|
format: 'cjs',
|
|
outfile: outputFile,
|
|
external: [
|
|
// Keep node: protocol imports external
|
|
'node:*',
|
|
],
|
|
loader: {
|
|
'.json': 'json', // Inline JSON files
|
|
},
|
|
banner: {
|
|
js: `// Polyfill for import.meta.url in CommonJS
|
|
var __filename = __filename || (() => {
|
|
try {
|
|
return require('url').fileURLToPath(__filename);
|
|
} catch (e) {
|
|
return __filename;
|
|
}
|
|
})();
|
|
var __dirname = __dirname || require('path').dirname(__filename);
|
|
var import_meta_url = typeof __filename !== 'undefined' ? require('url').pathToFileURL(__filename).href : undefined;
|
|
`,
|
|
},
|
|
define: {
|
|
'import.meta.url': 'import_meta_url',
|
|
},
|
|
minify: false, // Keep readable for debugging
|
|
sourcemap: false,
|
|
});
|
|
|
|
console.log('✓ Bundle created at:', outputFile);
|
|
return outputFile;
|
|
}
|
|
|
|
/**
|
|
* Build macOS binary and installer
|
|
*/
|
|
async function buildMacOS() {
|
|
console.log('Building macOS binary...');
|
|
|
|
// Step 1: Bundle with esbuild
|
|
const bundledFile = await bundleWithEsbuild();
|
|
|
|
// Step 2: Package with pkg
|
|
const targetPlatform = 'node20-macos-arm64';
|
|
const outputPath = join(DIST_DIR, 'safe-chain-macos-arm64');
|
|
|
|
// Copy shell-integration files to a staging directory with the structure we want in /snapshot
|
|
const stagingDir = join(DIST_DIR, 'staging');
|
|
const stagingShellInt = join(stagingDir, 'src/shell-integration');
|
|
|
|
// Clean and create staging directory
|
|
if (existsSync(stagingDir)) {
|
|
rmSync(stagingDir, { recursive: true });
|
|
}
|
|
mkdirSync(stagingShellInt, { recursive: true });
|
|
|
|
// Copy shell-integration directory
|
|
const shellIntegrationSrc = join(SAFE_CHAIN_DIR, 'src/shell-integration');
|
|
|
|
// Helper to copy directory recursively
|
|
const copyDir = (src, dest) => {
|
|
if (!existsSync(dest)) {
|
|
mkdirSync(dest, { recursive: true });
|
|
}
|
|
const entries = readdirSync(src, { withFileTypes: true });
|
|
for (const entry of entries) {
|
|
const srcPath = join(src, entry.name);
|
|
const destPath = join(dest, entry.name);
|
|
if (entry.isDirectory()) {
|
|
copyDir(srcPath, destPath);
|
|
} else {
|
|
copyFileSync(srcPath, destPath);
|
|
}
|
|
}
|
|
};
|
|
|
|
copyDir(shellIntegrationSrc, stagingShellInt);
|
|
|
|
const pkgArgs = [
|
|
bundledFile,
|
|
'--target', targetPlatform,
|
|
'--output', outputPath,
|
|
'--compress', 'GZip',
|
|
// Include contents of staging/src - files will be at /snapshot/src/shell-integration/
|
|
`--assets=${join(stagingDir, 'src')}/**/*`,
|
|
];
|
|
|
|
console.log(`Running: npx @yao-pkg/pkg ${pkgArgs.join(' ')}`);
|
|
|
|
// Use spawn instead of execFile to avoid issues with glob expansion
|
|
const { spawn } = await import('node:child_process');
|
|
|
|
const pkgProcess = spawn('npx', ['@yao-pkg/pkg', ...pkgArgs], {
|
|
cwd: __dirname,
|
|
stdio: 'inherit'
|
|
});
|
|
|
|
await new Promise((resolve, reject) => {
|
|
pkgProcess.on('close', (code) => {
|
|
if (code !== 0) {
|
|
reject(new Error(`pkg failed with code ${code}`));
|
|
} else {
|
|
resolve();
|
|
}
|
|
});
|
|
}); console.log('✓ Binary created at:', outputPath);
|
|
|
|
// Create installer package
|
|
await createMacOSInstaller(outputPath);
|
|
}
|
|
|
|
/**
|
|
* Create macOS installer with certificate installation
|
|
*/
|
|
async function createMacOSInstaller(binaryPath) {
|
|
console.log('Creating macOS installer package...');
|
|
|
|
const installerDir = join(DIST_DIR, 'macos-installer');
|
|
const scriptsDir = join(installerDir, 'scripts');
|
|
const resourcesDir = join(installerDir, 'resources');
|
|
|
|
// Create directory structure
|
|
mkdirSync(scriptsDir, { recursive: true });
|
|
mkdirSync(resourcesDir, { recursive: true });
|
|
|
|
// Copy binary to resources
|
|
const binaryDestination = join(resourcesDir, 'safe-chain');
|
|
copyFileSync(binaryPath, binaryDestination);
|
|
|
|
// Read installer scripts from separate files
|
|
const scriptsSourceDir = join(__dirname, 'scripts');
|
|
const preinstallScript = readFileSync(join(scriptsSourceDir, 'darwin_preinstall.sh'), 'utf8');
|
|
const postinstallScript = readFileSync(join(scriptsSourceDir, 'darwin_postinstall.sh'), 'utf8');
|
|
const uninstallScript = readFileSync(join(scriptsSourceDir, 'darwin_uninstall.sh'), 'utf8');
|
|
|
|
// Write scripts to installer directory
|
|
writeFileSync(join(scriptsDir, 'preinstall'), preinstallScript, { mode: 0o755 });
|
|
writeFileSync(join(scriptsDir, 'postinstall'), postinstallScript, { mode: 0o755 });
|
|
writeFileSync(join(installerDir, 'uninstall.sh'), uninstallScript, { mode: 0o755 });
|
|
|
|
console.log('✓ macOS installer package created at:', installerDir);
|
|
console.log('');
|
|
console.log('To create a .pkg installer, run:');
|
|
console.log(` cd ${installerDir}`);
|
|
console.log(' pkgbuild --root resources --scripts scripts --identifier com.aikido.safe-chain --version 1.0.0 --install-location /tmp/safe-chain-install SafeChain.pkg');
|
|
}
|
|
|
|
/**
|
|
* Build Linux binary and installer
|
|
*/
|
|
async function buildLinux() {
|
|
// TODO: Implement Linux binary creation
|
|
}
|
|
|
|
/**
|
|
* Build Windows binary and installer
|
|
*/
|
|
async function buildWindows() {
|
|
// TODO: Implement Windows binary creation
|
|
}
|
|
|
|
/**
|
|
* Main build function
|
|
*/
|
|
async function build() {
|
|
const { platform } = parseArgs();
|
|
|
|
console.log('=== Safe Chain Installer Builder ===');
|
|
console.log(`Platform: ${platform}`);
|
|
console.log('');
|
|
|
|
ensureDistDirectory();
|
|
|
|
try {
|
|
switch (platform) {
|
|
case 'macos':
|
|
await buildMacOS();
|
|
break;
|
|
case 'linux':
|
|
await buildLinux();
|
|
break;
|
|
case 'windows':
|
|
await buildWindows();
|
|
break;
|
|
default:
|
|
console.error(`Unknown platform: ${platform}`);
|
|
console.error('Valid options: macos, linux, windows');
|
|
process.exit(1);
|
|
}
|
|
} catch (error) {
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Run the build
|
|
build();
|