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
Markdown
# 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