UNPKG

executable-wrapper

Version:

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

367 lines (273 loc) • 12.4 kB
# Executable Wrapper A universal Node.js application packager that creates signed and notarized executables for macOS and Windows. ## šŸŽÆ Features - āœ… **Cross-platform** - Build for macOS (x64, arm64, universal) and Windows - āœ… **Code Signing** - Automatic signing with Developer ID certificates - āœ… **Notarization** - Apple notarization with automatic stapling - āœ… **Native Addons** - Full support for native Node.js modules (usb, node-hid, etc.) - āœ… **Universal Binaries** - True universal macOS apps and PKG installers supporting both Intel and Apple Silicon - āœ… **Terminal Control** - Optional `--terminal` parameter to force terminal execution in macOS apps - āœ… **Zero Configuration** - Single `.env` file drives the entire build process - āœ… **Production Ready** - Apps pass Gatekeeper and can be distributed immediately - āœ… **NPM Package Ready** - Works seamlessly as an npm dependency with proper path resolution ## šŸ“¦ What It Does This wrapper project packages any Node.js application into native executables: - **macOS**: `.app` bundles and `.pkg` installers (including universal binaries) - **Windows**: `.exe` executables No need to integrate build scripts into your project - just provide a `.env` configuration file and the wrapper handles everything. ## šŸš€ Quick Start ### 1. Install Dependencies ```bash cd executable-wrapper npm install ``` ### 2. Prepare Your Project Your Node.js project structure: ``` my-project/ ā”œā”€ā”€ dist/ │ └── index.js # Your bundled application ā”œā”€ā”€ resources/ │ ā”œā”€ā”€ appIcon.icns # macOS icon (optional) │ └── appIcon.ico # Windows icon (optional) ā”œā”€ā”€ pkg.json # pkg configuration (optional, for native modules) └── .env # Wrapper configuration ``` ### 3. Create `.env` Configuration Create a `.env` file in your project: ```env # Application Info APP_NAME=my-app BUNDLE_ID=com.company.myapp APP_DISPLAY_NAME=My Application APP_VERSION=1.0.0 # Build Configuration JS_ENTRY_POINT=dist/index.js PKG_CONFIG=pkg.json # Resources ICON_MACOS=resources/appIcon.icns ICON_WINDOWS=resources/appIcon.ico # macOS Signing & Notarization (required for --sign and --notarize) APPLE_TEAM_NAME="Your Company Name" APPLE_TEAM_ID=YOUR_TEAM_ID APPLE_ID=your-apple-id@example.com APPLE_ID_PASSWORD=xxxx-xxxx-xxxx-xxxx # macOS Configuration INSTALL_LOCATION=/Applications/Utilities MACOS_PERMISSIONS=network.client,device.usb # Company Info COMPANY_NAME=Your Company COPYRIGHT=Copyright Ā© 2024 Your Company ``` See `template.env` for a complete configuration example. ### 4. Build Your Application ```bash cd executable-wrapper # macOS Universal (both Intel and Apple Silicon) node index.js --env ../my-project/.env --platform macos-universal --format app # macOS with signing and notarization node index.js --env ../my-project/.env --platform macos-universal --format app --sign --notarize # Windows node index.js --env ../my-project/.env --platform win-x64 # macOS universal installer (.pkg) - supports both Intel and Apple Silicon node index.js --env ../my-project/.env --platform macos-universal --format pkg --sign ``` ### 5. Find Your Executable Output will be in your project's `executable-wrapper-app-releases/` directory: ``` my-project/executable-wrapper-app-releases/ ā”œā”€ā”€ my-app-1_0_0-macos-universal.app ā”œā”€ā”€ my-app-1_0_0-macos-universal.pkg └── my-app-1_0_0-win-x64.exe ``` ## šŸ“‹ CLI Options ```bash node index.js [options] Options: --env <path> Path to .env configuration file (required) --platform <type> Target platform: - macos-x64 (Intel Mac) - macos-arm64 (Apple Silicon) - macos-universal (Universal binary) - win-x64 (Windows) --format <type> Output format: - executable (binary only) - app (macOS .app bundle) - pkg (macOS installer) --sign Sign the application (requires credentials in .env) --notarize Notarize the application (implies --sign, macOS only) --terminal-script Generate wrapper script that shows terminal output (macOS only) --output <path> Custom output directory (optional) --help, -h Show help --version, -v Show version ``` ## šŸ” Code Signing & Notarization ### macOS Requirements For `--sign` and `--notarize` flags, you need: 1. **Apple Developer Account** with Developer ID certificates 2. **Certificates installed** in Keychain: - Developer ID Application (for .app) - Developer ID Installer (for .pkg) 3. **App-Specific Password** for notarization (generate at https://appleid.apple.com/account/manage) Add these to your `.env`: ```env APPLE_TEAM_NAME="Your Company Name" APPLE_TEAM_ID=ABC1234XYZ APPLE_ID=developer@company.com APPLE_ID_PASSWORD=xxxx-xxxx-xxxx-xxxx ``` ### What Happens During Signing & Notarization 1. **Signing** - Code signs the .app with your Developer ID 2. **Notarization** - Submits to Apple's notarization service 3. **Stapling** - Attaches the notarization ticket to the .app 4. **Verification** - Validates the signature and notarization Result: Your app passes Gatekeeper and can be distributed without warnings. ## šŸ”§ Native Modules If your app uses native Node.js modules (like `usb`, `node-hid`, `serialport`), create a `pkg.json` in your project root: ```json { "assets": [ "node_modules/usb/prebuilds/**/*.node", "node_modules/node-hid/prebuilds/**/*.node" ] } ``` **Important**: Asset paths in `pkg.json` are resolved relative to your **project root directory** (where your `.env` file is located), not relative to the JS entry point. Reference it in your `.env`: ```env PKG_CONFIG=pkg.json ``` ### Verifying Native Module Packaging To ensure native modules are properly included: 1. Place `pkg.json` in your project root (same directory as `.env`) 2. Use paths relative to the project root (e.g., `node_modules/*/prebuilds/**/*.node`) 3. Check that the prebuilt binaries exist in your `node_modules` directory ## šŸŽØ Application Icons ### macOS (.icns) Create a `.icns` file with multiple resolutions: - 16x16, 32x32, 64x64, 128x128, 256x256, 512x512, 1024x1024 Tools: Icon Composer, `iconutil`, or online converters. ### Windows (.ico) Create a `.ico` file with multiple resolutions: - 16x16, 32x32, 48x48, 64x64, 128x128, 256x256 Tools: GIMP, ImageMagick, or online converters. ## šŸ—ļø Project Structure ``` executable-wrapper/ ā”œā”€ā”€ index.js # Main entry point ā”œā”€ā”€ lib/ │ ā”œā”€ā”€ config.js # Configuration management │ ā”œā”€ā”€ builder.js # Executable builder (pkg) │ ā”œā”€ā”€ macos/ │ │ ā”œā”€ā”€ app-builder.js # .app bundle creator │ │ ā”œā”€ā”€ pkg-builder.js # .pkg installer creator │ │ ā”œā”€ā”€ universal-builder.js # Universal binary builder │ │ ā”œā”€ā”€ plist-generator.js # Info.plist generator │ │ ā”œā”€ā”€ entitlements-generator.js # Entitlements generator │ │ ā”œā”€ā”€ signer.js # Code signing │ │ └── notarizer.js # Notarization & stapling │ └── windows/ │ └── signer.js # Windows signing ā”œā”€ā”€ mac/ │ └── app.entitlements.example # Example entitlements file ā”œā”€ā”€ template.env # Configuration template ā”œā”€ā”€ package.json └── README.md ``` ## šŸ“š Configuration Reference ### Required Fields ```env APP_NAME=my-app # Application name (lowercase, no spaces) BUNDLE_ID=com.company.app # macOS bundle identifier APP_DISPLAY_NAME=My Application # Display name (user-facing) APP_VERSION=1.0.0 # Version number JS_ENTRY_POINT=dist/index.js # Path to your bundled JS file ``` ### Optional Fields ```env # Resources ICON_MACOS=resources/appIcon.icns # macOS icon ICON_WINDOWS=resources/appIcon.ico # Windows icon PKG_CONFIG=pkg.json # pkg configuration file # macOS Permissions (comma-separated) MACOS_PERMISSIONS=network.client,device.usb # Custom Entitlements ENTITLEMENTS_PATH=mac/custom.entitlements # Override default entitlements # Company Info COMPANY_NAME=My Company COPYRIGHT=Copyright Ā© 2024 My Company # macOS Installer INSTALL_LOCATION=/Applications/Utilities # .pkg install location ``` ### macOS Permissions Available permissions for `MACOS_PERMISSIONS`: - `network.client` - Outgoing network connections - `network.server` - Incoming network connections - `device.usb` - USB device access - `device.bluetooth` - Bluetooth access - `files.user-selected.read-write` - File access via dialogs - `files.downloads.read-write` - Downloads folder access ## šŸ–„ļø Terminal Control (macOS) For macOS applications, you can control the wrapper script behavior at build time: ### Build-Time Control Use the `--terminal-script` parameter during build to generate different wrapper scripts: ```bash # Default: Background execution (no terminal popup) node index.js --env ../myapp/.env --platform macos-universal --format app # Terminal script: Always shows terminal output node index.js --env ../myapp/.env --platform macos-universal --format app --terminal-script ``` ### Wrapper Script Types #### Default Script (Background Execution) - **From Terminal**: Runs in foreground (shows output) - **From Finder/GUI**: Runs in background (no terminal popup) - **Use case**: Regular applications that don't need terminal output #### Terminal Script (Always Shows Terminal) - **From Terminal**: Runs in foreground (shows output) - **From Finder/GUI**: Opens Terminal and runs in foreground (shows output) - **Use case**: CLI tools, debugging, interactive applications ### Use Cases - **Debugging**: See real-time output and error messages - **Interactive Apps**: CLI tools that need user input - **Development**: Testing and troubleshooting - **Logging**: Monitor application behavior ## šŸ” Troubleshooting ### "command not found: pkg" Solution: Run `npm install` in the wrapper project. ### "Missing signing configuration" Solution: Add `APPLE_TEAM_NAME` and `APPLE_TEAM_ID` to your `.env`. ### "Missing notarization configuration" Solution: Add `APPLE_ID` and `APPLE_ID_PASSWORD` to your `.env`. ### Notarization fails - Check your Apple ID and app-specific password - Ensure certificates are installed and valid - Check Apple's notarization logs for specific errors ### Native modules not working - Ensure `pkg.json` is in your **project root directory** (same location as `.env`) - Asset paths must be relative to the project root, not the entry point directory - Check that prebuilt binaries exist in `node_modules` - Verify the module supports your target platform - Example: If your entry point is `dist/index.js`, paths like `node_modules/usb/**/*.node` will resolve from the project root, not from `dist/` ## šŸ“– Examples Example usage with the test-app project can be found in the neighboring `test-app` directory. ## šŸ“„ License MIT License - See package.json for details. Ensure you have appropriate licenses and certificates for code signing when distributing applications. ## šŸ¤ Contributing This is a wrapper tool designed for internal use. For issues or improvements, please contact the maintainer. ## šŸ“ Changelog ### v1.3.1 (2025-10-09) - **Improved**: More robust pkg execution with explicit `lib-es5/bin.js` resolution and clearer logging - **Stability**: Native module rebuild failures no longer fail the overall build, only warn ### v1.3.0 (2025-01-27) - **Added**: Universal PKG support for macOS installers - **Added**: Automatic native module rebuilding for target platforms - **Improved**: Enhanced universal build capabilities with better architecture detection ### v1.2.0 (2025-10-09) - **Fixed**: pkg assets path resolution now uses project root instead of entry point directory - **Improved**: Native modules packaging for projects with JS entry points in subdirectories - This ensures `pkg.json` asset paths are correctly resolved from the project root See [CHANGELOG.md](CHANGELOG.md) for full version history. --- **Version**: 1.3.1 **Last Updated**: 2025-10-09