seabox 0.1.0-beta.3 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,22 +1,23 @@
1
1
  # seabox
2
2
 
3
- A reusable tool for building Node.js Single Executable Applications (SEA) with native-module support.
3
+ A reusable tool for building Node.js Single Executable Applications (SEA) with native-module support and binary extraction.
4
4
 
5
5
  ## Features
6
6
 
7
7
  - Bundle Node.js applications into standalone executables
8
- - Automatic native module (.node, .dll, .so, .dylib) extraction and loading
9
- - Asset encryption with obfuscated keys embedded in V8 snapshots
8
+ - **Automatic asset detection** from `path.join(__dirname, ...)` patterns
9
+ - **Automatic native module detection** (.node files) with pattern transforms
10
+ - Platform-specific library extraction (DLLs, shared libraries)
11
+ - Asset encryption with obfuscated keys
10
12
  - Multi-platform targeting (Windows, Linux, macOS)
11
13
  - V8 snapshot support for faster startup
12
14
  - Integrity checking for extracted binaries
13
15
  - Automatic code signature removal before injection
14
- - Simple configuration via package.json
15
16
 
16
17
  ## Use case
17
- This tooling was created as an alternative to pkg, which is unfortunatly deprecated, and where forks were running foul of virus checkers. By using node's SEA, the executables are directly from nodejs's distribution source, and built using node's native Single Executable Application solution. Unfortunatly this does mean native modules embedded within the exe cannot run directly and must be extracted to a location on the disk on first run - This tooling automates that process for you, while providing arbitrary asset embedding. Embedded assets are _not_ extracted and access to them is handled by intercepting require and fs.
18
+ This tooling was created as an alternative to pkg, which is unfortunatly deprecated, and where forks were running foul of virus checkers. By using node's SEA, the executables are directly downloaded from nodejs's distribution source, and built using node's native Single Executable Application solution. Unfortunatly this does mean native modules embedded within the exe cannot run directly and must be extracted to a location on the disk on first run - This tooling automates that process for you, while providing arbitrary asset embedding. Embedded assets are _not_ extracted and access to them is handled by intercepting require and fs.
18
19
 
19
- Note: **V8 snapshot includes and embedds the original source**, this is currently a limitation of Node's SEA tooling as far as I can tell; thus the snapshot is only useful for faster startup.
20
+ Note: **V8 snapshot includes and embedds the original source**, this is currently a limitation of Node's SEA tooling as far as I can tell; thus the snapshot is only useful for faster startup. Its possible to get around this by using bytenode's vm.script hack (embed the bytenode code as an asset and run another vm snapshot with the faux script input) and I'll look into supporting it in the future.
20
21
 
21
22
  ## Installation
22
23
 
@@ -26,65 +27,82 @@ npm install --save-dev seabox
26
27
 
27
28
  ## Configuration
28
29
 
29
- Add a `sea` configuration to your `package.json`:
30
+ Create a `seabox.config.json` file in your project root:
30
31
 
31
32
  ```json
32
33
  {
33
- "sea": {
34
- "entry": "./out/server.js",
35
- "assets": [
36
- "./out/client/**/*",
37
- "./out/lib/**/*",
38
- "./out/native/**/*",
39
- "!**/*.md",
40
- "!**/test/**"
41
- ],
42
- "binaries": [
43
- "*.node",
44
- "*.dll"
45
- ],
46
- "targets": [
47
- "node24.11.0-win32-x64"
48
- ],
49
- "output": "myapp.exe",
50
- "outputPath": "dist",
51
- "disableExperimentalSEAWarning": true,
52
- "useSnapshot": true,
53
- "useCodeCache": false
54
- }
34
+ "entry": "./src/index.js",
35
+ "outputs": [
36
+ {
37
+ "path": "./dist/win",
38
+ "target": "node24.11.0-win32-x64",
39
+ "output": "myapp.exe"
40
+ }
41
+ ],
42
+ "bundler": {
43
+ "external": []
44
+ },
45
+ "encryptAssets": false,
46
+ "useSnapshot": true,
47
+ "verbose": false
55
48
  }
56
49
  ```
57
50
 
58
51
  ## Configuration Options
59
52
 
60
- - **entry**: Path to your bundled application entry point
61
- - **assets**: Array of glob patterns for files to include (supports `!` prefix for exclusions, e.g., `"!**/*.md"`)
62
- - **binaries**: Patterns to identify binary files that need extraction (e.g., `.node`, `.dll`)
63
- - **targets**: Array of target platforms (format: `nodeX.Y.Z-platform-arch`)
64
- - **output**: Name of the output executable
65
- - **outputPath**: Directory for build output
66
- - **disableExperimentalSEAWarning**: Suppress Node.js SEA experimental warnings (default: true)
67
- - **useSnapshot**: Enable V8 snapshot for faster startup (default: false)
68
- - **useCodeCache**: Enable V8 code cache (default: false)
69
- - **encryptAssets**: Enable encryption for assets (default: false)
70
- - **encryptExclude**: Patterns to exclude from encryption (e.g., `['*.txt']`)
71
- - **rebuild**: Automatically rebuild native modules for the target platform before building the SEA (default: false)
72
- - **rcedit**: (Windows only) Customize executable icon and version information. See [rcedit options](#windows-executable-customization-rcedit)
73
- - **cacheLocation**: Custom cache directory for extracted binaries (default: `'./.sea-cache'`). Supports environment variable expansion (e.g., `'%LOCALAPPDATA%\\myapp-cache'` on Windows or `'$HOME/.cache/myapp'` on Unix)
53
+ | Option | Type | Required | Description |
54
+ |--------|------|----------|-------------|
55
+ | `entry` | `string` | Yes | Path to your application's entry point |
56
+ | `outputs` | `array` | Yes | Array of build targets |
57
+ | `outputs[].path` | `string` | Yes | Output directory for this target |
58
+ | `outputs[].target` | `string` | Yes | Build target (format: `nodeX.Y.Z-platform-arch`) |
59
+ | `outputs[].output` | `string` | Yes | Output filename |
60
+ | `outputs[].libraries` | `array` | No | Explicit glob patterns for shared libraries (DLLs/SOs) requiring filesystem extraction. Libraries referenced in code via `, ...)` are automatically detected. |
61
+ | `outputs[].rcedit` | `object` | No | Windows executable metadata (icon, version info) |
62
+ | `assets` | `array` | No | Glob patterns for assets to embed (merged with auto-detected assets) |
63
+ | `bundler` | `object` | No | Rolldown Bundler options |
64
+ | `bundler.external` | `array` | No | Modules to exclude from bundling |
65
+ | `bundler.plugins` | `array` | No | Additional Rolldown plugins |
66
+ | `bundler.minify` | `boolean` | No | Minify bundled code |
67
+ | `bundler.sourcemap` | `boolean` | No | Generate source maps |
68
+ | `encryptAssets` | `boolean` | No | Enable asset encryption (default: false) |
69
+ | `encryptExclude` | `array` | No | Glob patterns to exclude from encryption |
70
+ | `useSnapshot` | `boolean` | No | Enable V8 startup snapshots (default: true) |
71
+ | `useCodeCache` | `boolean` | No | Enable V8 code cache (default: false) |
72
+ | `cacheLocation` | `string` | No | Path for code cache storage |
73
+ | `sign` | `string` | No | Path to custom signing script (.mjs/.cjs) |
74
+ | `verbose` | `boolean` | No | Enable verbose logging (default: false) |
74
75
 
75
76
  ## Usage
76
77
 
77
- After installing `seabox` as a dev dependency and configuring your `package.json`, build your SEA executable:
78
+ ### CLI Commands
79
+
80
+ ```bash
81
+ # Build executable(s)
82
+ npx seabox build
83
+
84
+ # Build with verbose output
85
+ npx seabox build --verbose
86
+
87
+ # Specify custom config file
88
+ npx seabox build --config custom-config.json
78
89
 
79
- ### npm script (recommended)
90
+ # Initialize a new config file
91
+ npx seabox init
80
92
 
81
- Add a build script to your `package.json`:
93
+ # Show help
94
+ npx seabox help
95
+ ```
96
+
97
+ ### npm Scripts (Recommended)
98
+
99
+ Add to your `package.json`:
82
100
 
83
101
  ```json
84
102
  {
85
103
  "scripts": {
86
- "build:exe": "seabox",
87
- "build:exe:verbose": "seabox --verbose"
104
+ "build": "seabox build",
105
+ "build:verbose": "seabox build --verbose"
88
106
  }
89
107
  }
90
108
  ```
@@ -92,27 +110,13 @@ Add a build script to your `package.json`:
92
110
  Then run:
93
111
 
94
112
  ```bash
95
- npm run build:exe
96
- ```
97
-
98
- ### CLI
99
-
100
- ```bash
101
- # Build using package.json configuration
102
- npx seabox
103
-
104
- # Build using a standalone config file (alternative to package.json)
105
- npx seabox --config sea-config.json
106
-
107
- # Verbose output
108
- npx seabox --verbose
109
-
113
+ npm run build
110
114
  ```
111
115
 
112
116
  ### Programmatic API
113
117
 
114
118
  ```javascript
115
- const { build } = require('seabox');
119
+ import { build } from 'seabox';
116
120
 
117
121
  await build({
118
122
  projectRoot: process.cwd(),
@@ -122,118 +126,139 @@ await build({
122
126
 
123
127
  ## How It Works
124
128
 
125
- 1. **Asset Scanning**: Scans and resolves all files matching your asset patterns
126
- 2. **Manifest Generation**: Creates a runtime manifest with metadata for binary extraction
127
- 3. **Bootstrap Injection**: Prepends bootstrap code to handle native module extraction
128
- 4. **Blob Creation**: Uses Node.js SEA tooling to create the application blob
129
- 5. **Binary Fetching**: Downloads the target Node.js binary and removes its signature
130
- 6. **Injection**: Uses postject to inject the blob into the Node binary
131
- 7. **Output**: Produces a standalone executable ready for signing and distribution
129
+ seabox automates the entire SEA build process:
132
130
 
133
- ## Binary Extraction
131
+ 1. **Bundling** - Automatically bundles your app with Rolldown, detecting:
132
+ - Native module patterns (`bindings`, `node-gyp-build`, direct `.node` requires)
133
+ - Asset references via `path.join(__dirname, 'relative/path')`
134
+ - Additional
134
135
 
135
- Native modules (`.node`, `.dll`, `.so`, `.dylib`) are automatically:
136
- - Extracted to a cache directory on first run
137
- - Integrity-checked using SHA-256 hashes
138
- - Loaded via custom module resolution
136
+ 2. **Asset Collection** - Gathers assets from three sources:
137
+ - **Auto-detected**: Files referenced via `path.join(__dirname, ...)` patterns
138
+ - **Config globs**: Patterns specified in `assets` array
139
+ - **Libraries**: Platform-specific shared libraries (DLLs/SOs)
139
140
 
140
- ### Cache Location
141
+ 3. **Native Module Rebuilding** - Rebuilds native modules for target platform
141
142
 
142
- By default, binaries are extracted to: `./.sea-cache/<appname>/<version>-<platform>-<arch>`
143
+ 4. **Bootstrap Injection** - Adds runtime code for asset loading and native module extraction
143
144
 
144
- You can customize the cache location in your configuration:
145
+ 5. **SEA Blob Creation** - Packages everything using Node.js SEA tooling
145
146
 
146
- ```json
147
- {
148
- "sea": {
149
- "cacheLocation": "%LOCALAPPDATA%\\myapp-cache"
150
- }
151
- }
152
- ```
147
+ 6. **Binary Preparation** - Downloads target Node.js binary and removes code signature
153
148
 
154
- The cache location supports environment variable expansion:
155
- - **Windows**: `%LOCALAPPDATA%`, `%APPDATA%`, `%TEMP%`, etc.
156
- - **Unix/Linux/macOS**: `$HOME`, `$TMPDIR`, `${XDG_CACHE_HOME}`, etc.
149
+ 7. **Injection** - Uses `postject` to inject the blob into the Node.js binary
157
150
 
158
- **Override at runtime**: Set the `SEACACHE` environment variable to override the configured location:
151
+ 8. **Output** - Produces standalone executable(s) ready for distribution
159
152
 
160
- ```bash
161
- # Windows
162
- set SEACACHE=C:\custom\cache\path
163
- myapp.exe
153
+ ### Automatic Asset Detection
154
+
155
+ **Like pkg**, seabox automatically detects and embeds assets referenced in your code:
156
+
157
+ ```javascript
158
+ import path from 'path';
159
+ import { fileURLToPath } from 'url';
160
+
161
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
164
162
 
165
- # Unix/Linux/macOS
166
- export SEACACHE=/custom/cache/path
167
- ./myapp
163
+ // This asset will be automatically detected and embedded
164
+ const configPath = path.join(__dirname, '../config/app.json');
165
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
168
166
  ```
169
167
 
170
- ## Native Module Rebuilding
168
+ **Asset sources (merged and deduplicated):**
169
+ 1. **Auto-detected** from code analysis during bundling
170
+ 2. **Config globs** from `assets: ["./data/**/*", "./public/**/*"]`
171
+ 3. **Platform libraries** from `outputs[].libraries` (e.g., DLLs for Windows)
171
172
 
172
- If your project has native modules (e.g., `.node` bindings), you may need to rebuild them for the target Node.js version:
173
+ ### Native Module Support
173
174
 
174
- ```bash
175
- # Rebuild for a specific target
176
- npx seabox-rebuild --target node24.11.0-win32-x64
175
+ seabox automatically handles native modules without any configuration:
177
176
 
178
- # Rebuild with separate options
179
- npx seabox-rebuild --node-version 24.11.0 --platform linux --arch x64
177
+ **Supported patterns:**
178
+ - `require('bindings')('module')` - Standard bindings package
179
+ - `require('./build/Release/addon.node')` - Direct requires
180
+ - `require('node-gyp-build')(__dirname)` - Prebuild binaries
181
+ - `require('node-pre-gyp')` patterns - Pre-compiled binaries
180
182
 
181
- # Rebuild in a specific directory
182
- npx seabox-rebuild /path/to/project --target node24.11.0-linux-x64
183
- ```
183
+ **At runtime:**
184
+ - Native modules are extracted to a cache directory on first run
185
+ - Modules are integrity-checked with SHA-256 hashes
186
+ - Custom `require()` shim loads modules from cache
187
+
188
+ ### Platform-Specific Libraries
189
+
190
+ Libraries that require filesystem access (like DLLs loaded via `dlopen`) can be included in two ways:
184
191
 
185
- The rebuilder will:
186
- - Scan all dependencies for native modules (those with `binding.gyp` or `gypfile: true`)
187
- - Rebuild each one using `node-gyp` for the target platform and Node.js version
188
- - Download necessary headers for cross-compilation
192
+ **1. Automatic Detection (Recommended)**
189
193
 
190
- **Note**: Cross-compilation may require additional platform-specific build tools installed.
194
+ If your code references a DLL using `path.join(__dirname, ...)`, it will be automatically detected and included:
195
+
196
+ ```javascript
197
+ // This will be automatically detected during bundling
198
+ const dllPath = path.join(__dirname, './lib/RGDevice.dll');
199
+ ```
191
200
 
192
- ## Windows Executable Customization (rcedit)
201
+ **2. Explicit Glob Patterns**
193
202
 
194
- For Windows executables, you can customize the icon and version information using the `rcedit` configuration option:
203
+ You can also explicitly specify library patterns in your config:
195
204
 
196
205
  ```json
197
206
  {
198
- "sea": {
199
- "output": "myapp.exe",
200
- "targets": ["node24.11.0-win32-x64"],
201
- "rcedit": {
202
- "icon": ".\\assets\\myapp.ico",
203
- "file-version": "1.2.3.4",
204
- "product-version": "1.2.3.4",
205
- "version-string": {
206
- "CompanyName": "My Company",
207
- "FileDescription": "My Application",
208
- "ProductName": "MyApp",
209
- "InternalName": "myapp.exe",
210
- "OriginalFilename": "myapp.exe",
211
- "LegalCopyright": "Copyright (C) 2025 My Company"
212
- }
207
+ "outputs": [
208
+ {
209
+ "target": "node24.11.0-win32-x64",
210
+ "libraries": ["lib/*.dll"] // Manually specify DLLs to include
213
211
  }
214
- }
212
+ ]
215
213
  }
216
214
  ```
217
215
 
218
- ### rcedit Options
219
216
 
220
- - **icon**: Path to `.ico` file for the executable icon
221
- - **file-version**: File version in `X.X.X.X` format
222
- - **product-version**: Product version in `X.X.X.X` format
223
- - **version-string**: Object containing version string properties:
224
- - `CompanyName`: Company name
225
- - `FileDescription`: Description of the file
226
- - `ProductName`: Product name
227
- - `InternalName`: Internal name
228
- - `OriginalFilename`: Original filename
229
- - `LegalCopyright`: Copyright notice
230
- - `LegalTrademarks`: Trademark information (optional)
231
- - `PrivateBuild`: Private build description (optional)
232
- - `SpecialBuild`: Special build description (optional)
217
+ These files are extracted on first run (like `.node` files) since they need to be loaded from the filesystem.
218
+
219
+ ### Code Signature Removal
220
+
221
+ Required before SEA injection. Platform-specific tools needed:
222
+ - **Windows**: `signtool.exe` (from Windows SDK)
223
+ - **macOS**: `codesign` (included with Xcode)
224
+ - **Linux**: Not required
225
+
226
+ ### Custom Signing
227
+
228
+ You can apply code signing after the build completes by specifying a custom signing script:
229
+
230
+ ```json
231
+ {
232
+ "sign": "./scripts/sign.mjs"
233
+ }
234
+ ```
233
235
 
234
- The rcedit step runs after signature removal and before the SEA blob injection. This only works for Windows (`win32`) targets.
236
+ The signing script must export a default function that receives a config object:
237
+
238
+ ```javascript
239
+ // scripts/sign.mjs
240
+ export default async function sign(config) {
241
+ const { exePath, target, platform, arch, nodeVersion, projectRoot } = config;
242
+
243
+ // Example: Windows code signing with signtool
244
+ if (platform === 'win32') {
245
+ execSync(`signtool sign /fd SHA256 /a "${exePath}"`);
246
+ }
247
+
248
+ // Example: macOS code signing
249
+ if (platform === 'darwin') {
250
+ execSync(`codesign --force --sign "Developer ID" "${exePath}"`);
251
+ }
252
+ }
253
+ ```
235
254
 
236
- For more details, see the [rcedit documentation](https://github.com/electron/rcedit).
255
+ **Config parameters:**
256
+ - `exePath` - Absolute path to the built executable
257
+ - `target` - Full target string (e.g., "node24.11.0-win32-x64")
258
+ - `platform` - Platform name (win32, linux, darwin)
259
+ - `arch` - Architecture (x64, arm64)
260
+ - `nodeVersion` - Node.js version
261
+ - `projectRoot` - Absolute path to project root
237
262
 
238
263
  ## Asset Encryption
239
264
 
@@ -254,25 +279,23 @@ seabox supports optional AES-256-GCM encryption of embedded assets to protect yo
254
279
  1. **Build Time**: A random 256-bit encryption key is generated
255
280
  2. **Asset Encryption**: Non-binary assets are encrypted using AES-256-GCM
256
281
  3. **Key Embedding**: The encryption key is obfuscated and embedded in the bootstrap code
257
- 4. **Key Obfuscation**: the bootstrap and key code are obfuscated, but not removed
282
+ 4. **Key Obfuscation**: the bootstrap and key code are obfuscated
258
283
  5. **Runtime Decryption**: Assets are transparently decrypted when accessed
259
284
 
260
285
  ### Considerations
261
286
 
262
287
  - **Binary files** (`.node`, `.dll`, `.so`, `.dylib`) are **never encrypted** as they must be extracted as-is
263
288
  - The manifest (`sea-manifest.json`) is **not encrypted** to allow bootstrap initialization
264
- - **V8 snapshot includes the original source**, this is currently a limitation of Node's SEA.
289
+ - **V8 snapshot includes the original source**, this is currently a limitation of Node's SEA tooling.
265
290
  - Encryption provides **obfuscation**, not cryptographic security against determined attackers
266
291
  - The bootloader code, that includes the encryption key, is obfuscated in the source embedded by Node's SEA
267
292
 
293
+ ## Contributing
268
294
 
269
- ## Platform Support
270
-
271
- - **Windows**: `win32-x64`, `win32-arm64`
272
- - **Linux**: `linux-x64`, `linux-arm64`
273
- - **macOS**: `darwin-x64`, `darwin-arm64`
295
+ Contributions welcome! Please open an issue or PR on [GitHub](https://github.com/MeirionHughes/seabox).
274
296
 
275
297
  ## License
276
298
 
277
299
  MIT
278
- Copyright Meirion Hughes 2025
300
+
301
+ Copyright © 2025 Meirion Hughes
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * seabox-rebuild.mjs
4
+ * Rebuild native modules for target platform/architecture
5
+ */
6
+
7
+ import { execSync } from 'child_process';
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+ import { fileURLToPath } from 'url';
11
+ import * as diag from '../lib/diagnostics.mjs';
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+
16
+ /**
17
+ * Rebuild a native module for a specific target
18
+ * @param {string} modulePath - Path to the native module
19
+ * @param {string} platform - Target platform (win32, linux, darwin)
20
+ * @param {string} arch - Target architecture (x64, arm64)
21
+ * @param {boolean} verbose - Enable verbose logging
22
+ */
23
+ function rebuildNativeModule(modulePath, platform, arch, verbose = false) {
24
+ diag.setVerbose(verbose);
25
+
26
+ diag.verbose(`Rebuilding native module: ${modulePath}`);
27
+ diag.verbose(`Target: ${platform}-${arch}`);
28
+
29
+ const packageJsonPath = path.join(modulePath, 'package.json');
30
+ if (!fs.existsSync(packageJsonPath)) {
31
+ throw new Error(`No package.json found in ${modulePath}`);
32
+ }
33
+
34
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
35
+ const moduleName = pkg.name;
36
+
37
+ // Check if module has native bindings
38
+ const hasBindingGyp = fs.existsSync(path.join(modulePath, 'binding.gyp'));
39
+ if (!hasBindingGyp && !pkg.gypfile) {
40
+ diag.verbose(`Module ${moduleName} does not appear to have native bindings, skipping`);
41
+ return;
42
+ }
43
+
44
+ try {
45
+ // Use node-gyp to rebuild for the target platform
46
+ const cmd = `npx node-gyp rebuild --target_platform=${platform} --target_arch=${arch}`;
47
+
48
+ diag.verbose(`Running: ${cmd}`);
49
+
50
+ execSync(cmd, {
51
+ cwd: modulePath,
52
+ stdio: verbose ? 'inherit' : 'pipe',
53
+ env: {
54
+ ...process.env,
55
+ npm_config_target_platform: platform,
56
+ npm_config_target_arch: arch
57
+ }
58
+ });
59
+
60
+ diag.verbose(`Successfully rebuilt ${moduleName}`);
61
+ } catch (error) {
62
+ diag.verbose(`Failed to rebuild ${moduleName}: ${error.message}`);
63
+ throw error;
64
+ }
65
+ }
66
+
67
+ // CLI entry point
68
+ if (import.meta.url === `file://${process.argv[1]}`) {
69
+ const args = process.argv.slice(2);
70
+
71
+ if (args.length < 3) {
72
+ diag.error('Usage: seabox-rebuild <module-path> <platform> <arch> [--verbose]');
73
+ process.exit(1);
74
+ }
75
+
76
+ const [modulePath, platform, arch] = args;
77
+ const verbose = args.includes('--verbose') || args.includes('-v');
78
+
79
+ try {
80
+ rebuildNativeModule(modulePath, platform, arch, verbose);
81
+ process.exit(0);
82
+ } catch (error) {
83
+ diag.error(`Rebuild failed: ${error.message}`);
84
+ process.exit(1);
85
+ }
86
+ }
87
+
88
+ export { rebuildNativeModule };