UNPKG

executable-wrapper

Version:

Universal packaging tool that converts Node.js projects into native executables for macOS (.app/.pkg) and Windows (.exe)

195 lines (173 loc) • 6.72 kB
#!/usr/bin/env node /** * Executable Wrapper - Main Entry Point * * Usage: * node index.js --env /path/to/project/.env [--platform macos-x64|macos-arm64|win-x64] [--format app|pkg|exe] * * Examples: * node index.js --env ../myproject/.env --platform macos-x64 --format app * node index.js --env ../myproject/.env --platform win-x64 */ const yargs = require('yargs/yargs'); const { hideBin } = require('yargs/helpers'); const path = require('path'); const fs = require('fs'); const { loadConfig } = require('./lib/config'); const { buildExecutable } = require('./lib/builder'); const { createMacOSApp } = require('./lib/macos/app-builder'); const { createMacOSPkg } = require('./lib/macos/pkg-builder'); const { createUniversalApp } = require('./lib/macos/universal-builder'); const { createUniversalPkg } = require('./lib/macos/universal-pkg-builder'); const { signMacOS } = require('./lib/macos/signer'); const { notarizeMacOS } = require('./lib/macos/notarizer'); const { signWindows } = require('./lib/windows/signer'); // Parse command line arguments const argv = yargs(hideBin(process.argv)) .option('env', { alias: 'e', type: 'string', description: 'Path to the .env configuration file', demandOption: true, }) .option('platform', { alias: 'p', type: 'string', description: 'Target platform', choices: ['macos-x64', 'macos-arm64', 'macos-universal', 'win-x64'], default: 'macos-x64', }) .option('format', { alias: 'f', type: 'string', description: 'Output format', choices: ['executable', 'app', 'pkg', 'exe'], default: 'executable', }) .option('sign', { type: 'boolean', description: 'Sign the output (requires signing configuration)', default: false, }) .option('notarize', { type: 'boolean', description: 'Notarize the output (macOS only, requires notarization configuration)', default: false, }) .option('terminal-script', { type: 'boolean', description: 'Generate wrapper script that shows terminal output (macOS only)', default: false, }) .option('output', { alias: 'o', type: 'string', description: 'Output directory (optional, defaults to project releases/ directory)', }) .example('$0 --env ../myapp/.env', 'Build executable for default platform') .example('$0 --env ../myapp/.env --platform macos-arm64 --format app --sign --notarize', 'Build signed and notarized macOS .app') .example('$0 --env ../myapp/.env --platform win-x64 --sign', 'Build signed Windows executable') .help() .alias('help', 'h') .version() .alias('version', 'v') .argv; async function main() { try { console.log('šŸš€ Executable Wrapper\n'); // Resolve and validate .env path const envPath = path.resolve(process.cwd(), argv.env); if (!fs.existsSync(envPath)) { throw new Error(`Environment file not found: ${envPath}`); } console.log(`šŸ“‹ Configuration:`); console.log(` Environment: ${envPath}`); console.log(` Platform: ${argv.platform}`); console.log(` Format: ${argv.format}`); console.log(` Sign: ${argv.sign ? 'Yes' : 'No'}`); console.log(` Notarize: ${argv.notarize ? 'Yes' : 'No'}`); console.log(` Terminal Script: ${argv.terminalScript ? 'Yes' : 'No'}`); console.log(''); // Load configuration console.log('šŸ“– Loading configuration...'); const config = loadConfig(envPath); console.log(` App: ${config.app.displayName} (${config.app.name})`); console.log(` Version: ${config.version.full}`); console.log(` Project: ${config.paths.projectRoot}`); console.log(''); // Determine output directory const outputDir = argv.output ? path.resolve(process.cwd(), argv.output) : path.join(config.paths.projectRoot, 'executable-wrapper-app-releases'); config.paths.outputDir = outputDir; let finalOutputPath; // Handle universal builds specially if (argv.platform === 'macos-universal') { if (argv.format === 'app') { // Universal .app builds both architectures internally finalOutputPath = await createUniversalApp(config, argv.terminalScript); } else if (argv.format === 'pkg') { // Universal .pkg builds both architectures internally finalOutputPath = await createUniversalPkg(config, argv.terminalScript); } else { throw new Error('Universal builds only support --format app or --format pkg'); } } else { // Normal single-architecture build console.log('šŸ”Ø Building executable...'); const executablePath = await buildExecutable(config, argv.platform); console.log(` āœ… Built: ${executablePath}\n`); finalOutputPath = executablePath; // Create app/pkg for macOS if (argv.platform.startsWith('macos')) { if (argv.format === 'app') { console.log('šŸ“¦ Creating .app bundle...'); finalOutputPath = await createMacOSApp(config, executablePath, argv.platform); console.log(` āœ… Created: ${finalOutputPath}\n`); } else if (argv.format === 'pkg') { console.log('šŸ“¦ Creating .pkg installer...'); finalOutputPath = await createMacOSPkg(config, executablePath, argv.platform); console.log(` āœ… Created: ${finalOutputPath}\n`); } } } // Sign if requested if (argv.sign) { if (argv.platform.startsWith('macos')) { console.log('šŸ” Signing...'); const signType = argv.platform === 'macos-universal' ? 'app' : argv.format; await signMacOS(config, finalOutputPath, signType); console.log(` āœ… Signed\n`); } else if (argv.platform.startsWith('win')) { console.log('šŸ” Signing...'); await signWindows(config, finalOutputPath); console.log(` āœ… Signed\n`); } } // Notarize if requested (macOS only) if (argv.notarize && argv.platform.startsWith('macos')) { if (!argv.sign) { console.warn('āš ļø Notarization requires signing. Skipping notarization.\n'); } else { console.log('šŸ“ Notarizing...'); const notarizeType = argv.platform === 'macos-universal' ? 'app' : argv.format; await notarizeMacOS(config, finalOutputPath, notarizeType); console.log(` āœ… Notarized\n`); } } console.log('šŸŽ‰ Done!\n'); console.log(`šŸ“ Output: ${finalOutputPath}`); console.log(''); } catch (error) { console.error('\nāŒ Error:', error.message); if (process.env.DEBUG) { console.error(error.stack); } process.exit(1); } } // Run if (require.main === module) { main(); } module.exports = { main };