seabox 0.1.0-beta.4 ā 0.1.1
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 +71 -235
- package/bin/seabox-rebuild.mjs +11 -18
- package/bin/seabox.mjs +39 -27
- package/lib/blob.mjs +2 -1
- package/lib/build.mjs +8 -6
- package/lib/config.mjs +11 -2
- package/lib/diagnostics.mjs +203 -0
- package/lib/fetch-node.mjs +5 -4
- package/lib/inject.mjs +32 -37
- package/lib/multi-target-builder.mjs +188 -103
- package/lib/native-scanner.mjs +7 -14
- package/lib/require-shim.mjs +7 -5
- package/lib/rolldown-bundler.mjs +13 -17
- package/lib/unsign.cjs +26 -16
- package/package.json +10 -6
package/README.md
CHANGED
|
@@ -25,6 +25,16 @@ Note: **V8 snapshot includes and embedds the original source**, this is currentl
|
|
|
25
25
|
npm install --save-dev seabox
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
### Optional: Windows Executable Metadata
|
|
29
|
+
|
|
30
|
+
If you want to customize Windows executable metadata (icon, version info), install `rcedit`:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install --save-dev rcedit
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
This is only needed if you use the `rcedit` configuration option.
|
|
37
|
+
|
|
28
38
|
## Configuration
|
|
29
39
|
|
|
30
40
|
Create a `seabox.config.json` file in your project root:
|
|
@@ -57,12 +67,12 @@ Create a `seabox.config.json` file in your project root:
|
|
|
57
67
|
| `outputs[].path` | `string` | Yes | Output directory for this target |
|
|
58
68
|
| `outputs[].target` | `string` | Yes | Build target (format: `nodeX.Y.Z-platform-arch`) |
|
|
59
69
|
| `outputs[].output` | `string` | Yes | Output filename |
|
|
60
|
-
| `outputs[].libraries` | `array` | No |
|
|
70
|
+
| `outputs[].libraries` | `array` | No | Explicit glob patterns for shared libraries (DLLs/SOs) requiring filesystem extraction. Libraries referenced in code via `, ...)` are automatically detected. |
|
|
61
71
|
| `outputs[].rcedit` | `object` | No | Windows executable metadata (icon, version info) |
|
|
62
72
|
| `assets` | `array` | No | Glob patterns for assets to embed (merged with auto-detected assets) |
|
|
63
|
-
| `bundler` | `object` | No | Bundler options |
|
|
73
|
+
| `bundler` | `object` | No | Rolldown Bundler options |
|
|
64
74
|
| `bundler.external` | `array` | No | Modules to exclude from bundling |
|
|
65
|
-
| `bundler.plugins` | `array` | No | Additional
|
|
75
|
+
| `bundler.plugins` | `array` | No | Additional Rolldown plugins |
|
|
66
76
|
| `bundler.minify` | `boolean` | No | Minify bundled code |
|
|
67
77
|
| `bundler.sourcemap` | `boolean` | No | Generate source maps |
|
|
68
78
|
| `encryptAssets` | `boolean` | No | Enable asset encryption (default: false) |
|
|
@@ -70,6 +80,7 @@ Create a `seabox.config.json` file in your project root:
|
|
|
70
80
|
| `useSnapshot` | `boolean` | No | Enable V8 startup snapshots (default: true) |
|
|
71
81
|
| `useCodeCache` | `boolean` | No | Enable V8 code cache (default: false) |
|
|
72
82
|
| `cacheLocation` | `string` | No | Path for code cache storage |
|
|
83
|
+
| `sign` | `string` | No | Path to custom signing script (.mjs/.cjs) |
|
|
73
84
|
| `verbose` | `boolean` | No | Enable verbose logging (default: false) |
|
|
74
85
|
|
|
75
86
|
## Usage
|
|
@@ -127,9 +138,10 @@ await build({
|
|
|
127
138
|
|
|
128
139
|
seabox automates the entire SEA build process:
|
|
129
140
|
|
|
130
|
-
1. **Bundling** - Automatically bundles your app with
|
|
141
|
+
1. **Bundling** - Automatically bundles your app with Rolldown, detecting:
|
|
131
142
|
- Native module patterns (`bindings`, `node-gyp-build`, direct `.node` requires)
|
|
132
143
|
- Asset references via `path.join(__dirname, 'relative/path')`
|
|
144
|
+
- Additional
|
|
133
145
|
|
|
134
146
|
2. **Asset Collection** - Gathers assets from three sources:
|
|
135
147
|
- **Auto-detected**: Files referenced via `path.join(__dirname, ...)` patterns
|
|
@@ -163,11 +175,6 @@ const configPath = path.join(__dirname, '../config/app.json');
|
|
|
163
175
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
164
176
|
```
|
|
165
177
|
|
|
166
|
-
**Detection works with:**
|
|
167
|
-
- `path.join(__dirname, 'relative/path')`
|
|
168
|
-
- `path.resolve(__dirname, 'relative/path')`
|
|
169
|
-
- Multiple path segments: `path.join(__dirname, '..', 'assets', 'file.txt')`
|
|
170
|
-
|
|
171
178
|
**Asset sources (merged and deduplicated):**
|
|
172
179
|
1. **Auto-detected** from code analysis during bundling
|
|
173
180
|
2. **Config globs** from `assets: ["./data/**/*", "./public/**/*"]`
|
|
@@ -187,38 +194,81 @@ seabox automatically handles native modules without any configuration:
|
|
|
187
194
|
- Native modules are extracted to a cache directory on first run
|
|
188
195
|
- Modules are integrity-checked with SHA-256 hashes
|
|
189
196
|
- Custom `require()` shim loads modules from cache
|
|
190
|
-
- Works transparently with packages like `better-sqlite3`, `sharp`, `canvas`, etc.
|
|
191
|
-
|
|
192
197
|
|
|
193
198
|
### Platform-Specific Libraries
|
|
194
199
|
|
|
195
|
-
Libraries that require filesystem access (like DLLs
|
|
200
|
+
Libraries that require filesystem access (like DLLs loaded via `dlopen`) can be included in two ways:
|
|
201
|
+
|
|
202
|
+
**1. Automatic Detection (Recommended)**
|
|
203
|
+
|
|
204
|
+
If your code references a DLL using `path.join(__dirname, ...)`, it will be automatically detected and included:
|
|
205
|
+
|
|
206
|
+
```javascript
|
|
207
|
+
// This will be automatically detected during bundling
|
|
208
|
+
const dllPath = path.join(__dirname, './lib/RGDevice.dll');
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**2. Explicit Glob Patterns**
|
|
212
|
+
|
|
213
|
+
You can also explicitly specify library patterns in your config:
|
|
196
214
|
|
|
197
215
|
```json
|
|
198
216
|
{
|
|
199
217
|
"outputs": [
|
|
200
218
|
{
|
|
201
219
|
"target": "node24.11.0-win32-x64",
|
|
202
|
-
"libraries": ["
|
|
220
|
+
"libraries": ["lib/*.dll"] // Manually specify DLLs to include
|
|
203
221
|
}
|
|
204
222
|
]
|
|
205
223
|
}
|
|
206
224
|
```
|
|
207
225
|
|
|
208
|
-
**Defaults by platform:**
|
|
209
|
-
- **Windows**: `**/*.dll`
|
|
210
|
-
- **Linux**: `**/*.so`, `**/*.so.*`
|
|
211
|
-
- **macOS**: `**/*.dylib`
|
|
212
226
|
|
|
213
227
|
These files are extracted on first run (like `.node` files) since they need to be loaded from the filesystem.
|
|
214
228
|
|
|
215
229
|
### Code Signature Removal
|
|
216
|
-
|
|
217
|
-
Required before SEA injection. Platform-specific tools needed:
|
|
230
|
+
If you have sign tools available, the seabox will attempt to unsign the node exe before modifying it. This is to reduce issues afterward when you try to resign it.
|
|
218
231
|
- **Windows**: `signtool.exe` (from Windows SDK)
|
|
219
232
|
- **macOS**: `codesign` (included with Xcode)
|
|
220
233
|
- **Linux**: Not required
|
|
221
234
|
|
|
235
|
+
### Custom Signing
|
|
236
|
+
|
|
237
|
+
You can apply code signing after the build completes by specifying a custom signing script:
|
|
238
|
+
|
|
239
|
+
```json
|
|
240
|
+
{
|
|
241
|
+
"sign": "./scripts/sign.mjs"
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
The signing script must export a default function that receives a config object:
|
|
246
|
+
|
|
247
|
+
```javascript
|
|
248
|
+
// scripts/sign.mjs
|
|
249
|
+
export default async function sign(config) {
|
|
250
|
+
const { exePath, target, platform, arch, nodeVersion, projectRoot } = config;
|
|
251
|
+
|
|
252
|
+
// Example: Windows code signing with signtool
|
|
253
|
+
if (platform === 'win32') {
|
|
254
|
+
execSync(`signtool sign /fd SHA256 /a "${exePath}"`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Example: macOS code signing
|
|
258
|
+
if (platform === 'darwin') {
|
|
259
|
+
execSync(`codesign --force --sign "Developer ID" "${exePath}"`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**Config parameters:**
|
|
265
|
+
- `exePath` - Absolute path to the built executable
|
|
266
|
+
- `target` - Full target string (e.g., "node24.11.0-win32-x64")
|
|
267
|
+
- `platform` - Platform name (win32, linux, darwin)
|
|
268
|
+
- `arch` - Architecture (x64, arm64)
|
|
269
|
+
- `nodeVersion` - Node.js version
|
|
270
|
+
- `projectRoot` - Absolute path to project root
|
|
271
|
+
|
|
222
272
|
## Asset Encryption
|
|
223
273
|
|
|
224
274
|
seabox supports optional AES-256-GCM encryption of embedded assets to protect your application code and data:
|
|
@@ -238,231 +288,17 @@ seabox supports optional AES-256-GCM encryption of embedded assets to protect yo
|
|
|
238
288
|
1. **Build Time**: A random 256-bit encryption key is generated
|
|
239
289
|
2. **Asset Encryption**: Non-binary assets are encrypted using AES-256-GCM
|
|
240
290
|
3. **Key Embedding**: The encryption key is obfuscated and embedded in the bootstrap code
|
|
241
|
-
4. **Key Obfuscation**: the bootstrap and key code are obfuscated
|
|
291
|
+
4. **Key Obfuscation**: the bootstrap and key code are obfuscated
|
|
242
292
|
5. **Runtime Decryption**: Assets are transparently decrypted when accessed
|
|
243
293
|
|
|
244
294
|
### Considerations
|
|
245
295
|
|
|
246
296
|
- **Binary files** (`.node`, `.dll`, `.so`, `.dylib`) are **never encrypted** as they must be extracted as-is
|
|
247
297
|
- The manifest (`sea-manifest.json`) is **not encrypted** to allow bootstrap initialization
|
|
248
|
-
- **V8 snapshot includes the original source**, this is currently a limitation of Node's SEA.
|
|
298
|
+
- **V8 snapshot includes the original source**, this is currently a limitation of Node's SEA tooling.
|
|
249
299
|
- Encryption provides **obfuscation**, not cryptographic security against determined attackers
|
|
250
300
|
- The bootloader code, that includes the encryption key, is obfuscated in the source embedded by Node's SEA
|
|
251
301
|
|
|
252
|
-
|
|
253
|
-
## Platform Support
|
|
254
|
-
|
|
255
|
-
- **Windows**: `win32-x64`, `win32-arm64`
|
|
256
|
-
- **Linux**: `linux-x64`, `linux-arm64`
|
|
257
|
-
- **macOS**: `darwin-x64`, `darwin-arm64`
|
|
258
|
-
|
|
259
|
-
## License
|
|
260
|
-
|
|
261
|
-
MIT
|
|
262
|
-
Copyright Meirion Hughes 2025
|
|
263
|
-
## Examples
|
|
264
|
-
|
|
265
|
-
### Basic Application
|
|
266
|
-
|
|
267
|
-
```javascript
|
|
268
|
-
// src/index.js
|
|
269
|
-
console.log('Hello from SEA!');
|
|
270
|
-
console.log('Platform:', process.platform);
|
|
271
|
-
console.log('Architecture:', process.arch);
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
```json
|
|
275
|
-
// seabox.config.json
|
|
276
|
-
{
|
|
277
|
-
"entry": "./src/index.js",
|
|
278
|
-
"outputs": [
|
|
279
|
-
{
|
|
280
|
-
"path": "./dist",
|
|
281
|
-
"target": "node24.11.0-win32-x64",
|
|
282
|
-
"output": "hello.exe"
|
|
283
|
-
}
|
|
284
|
-
]
|
|
285
|
-
}
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
### With Assets (Auto-Detection)
|
|
289
|
-
|
|
290
|
-
```javascript
|
|
291
|
-
// src/index.js
|
|
292
|
-
import fs from 'fs';
|
|
293
|
-
import path from 'path';
|
|
294
|
-
import { fileURLToPath } from 'url';
|
|
295
|
-
|
|
296
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
297
|
-
|
|
298
|
-
// Assets referenced via path.join(__dirname, ...) are auto-detected
|
|
299
|
-
const configPath = path.join(__dirname, '../config/settings.json');
|
|
300
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
301
|
-
|
|
302
|
-
console.log('Config loaded:', config);
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
No configuration needed - the asset is automatically detected and embedded!
|
|
306
|
-
|
|
307
|
-
### With Config Assets
|
|
308
|
-
|
|
309
|
-
```json
|
|
310
|
-
{
|
|
311
|
-
"entry": "./src/index.js",
|
|
312
|
-
"outputs": [
|
|
313
|
-
{
|
|
314
|
-
"path": "./dist",
|
|
315
|
-
"target": "node24.11.0-win32-x64",
|
|
316
|
-
"output": "myapp.exe"
|
|
317
|
-
}
|
|
318
|
-
],
|
|
319
|
-
"assets": [
|
|
320
|
-
"./public/**/*",
|
|
321
|
-
"./data/**/*.json",
|
|
322
|
-
"!**/*.md"
|
|
323
|
-
]
|
|
324
|
-
}
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
All files matching the glob patterns will be embedded. Auto-detected assets are merged automatically.
|
|
328
|
-
|
|
329
|
-
### With Native Modules
|
|
330
|
-
|
|
331
|
-
```javascript
|
|
332
|
-
// src/index.js
|
|
333
|
-
import Database from 'better-sqlite3';
|
|
334
|
-
|
|
335
|
-
const db = new Database(':memory:');
|
|
336
|
-
db.exec('CREATE TABLE users (name TEXT)');
|
|
337
|
-
db.prepare('INSERT INTO users VALUES (?)').run('Alice');
|
|
338
|
-
|
|
339
|
-
const users = db.prepare('SELECT * FROM users').all();
|
|
340
|
-
console.log('Users:', users);
|
|
341
|
-
|
|
342
|
-
db.close();
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
No special configuration needed - seabox automatically detects and handles the native module!
|
|
346
|
-
|
|
347
|
-
### Multi-Platform Build
|
|
348
|
-
|
|
349
|
-
```json
|
|
350
|
-
{
|
|
351
|
-
"entry": "./src/index.js",
|
|
352
|
-
"outputs": [
|
|
353
|
-
{
|
|
354
|
-
"path": "./dist/win",
|
|
355
|
-
"target": "node24.11.0-win32-x64",
|
|
356
|
-
"output": "myapp.exe"
|
|
357
|
-
},
|
|
358
|
-
{
|
|
359
|
-
"path": "./dist/linux",
|
|
360
|
-
"target": "node24.11.0-linux-x64",
|
|
361
|
-
"output": "myapp"
|
|
362
|
-
},
|
|
363
|
-
{
|
|
364
|
-
"path": "./dist/macos",
|
|
365
|
-
"target": "node24.11.0-darwin-arm64",
|
|
366
|
-
"output": "myapp"
|
|
367
|
-
}
|
|
368
|
-
],
|
|
369
|
-
"bundler": {
|
|
370
|
-
"external": []
|
|
371
|
-
},
|
|
372
|
-
"useSnapshot": true
|
|
373
|
-
}
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
Run `seabox build` and get executables for all three platforms!
|
|
377
|
-
|
|
378
|
-
## Advanced Features
|
|
379
|
-
|
|
380
|
-
### Asset Encryption
|
|
381
|
-
|
|
382
|
-
Protect your source code with AES-256-GCM encryption:
|
|
383
|
-
|
|
384
|
-
```json
|
|
385
|
-
{
|
|
386
|
-
"entry": "./src/index.js",
|
|
387
|
-
"outputs": [
|
|
388
|
-
{
|
|
389
|
-
"path": "./dist",
|
|
390
|
-
"target": "node24.11.0-win32-x64",
|
|
391
|
-
"output": "myapp.exe"
|
|
392
|
-
}
|
|
393
|
-
],
|
|
394
|
-
"encryptAssets": true,
|
|
395
|
-
"encryptExclude": ["*.txt"]
|
|
396
|
-
}
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
### External Dependencies
|
|
400
|
-
|
|
401
|
-
Exclude packages from bundling:
|
|
402
|
-
|
|
403
|
-
```json
|
|
404
|
-
{
|
|
405
|
-
"entry": "./src/index.js",
|
|
406
|
-
"outputs": [
|
|
407
|
-
{
|
|
408
|
-
"path": "./dist",
|
|
409
|
-
"target": "node24.11.0-win32-x64",
|
|
410
|
-
"output": "myapp.exe"
|
|
411
|
-
}
|
|
412
|
-
],
|
|
413
|
-
"bundler": {
|
|
414
|
-
"external": ["fsevents", "some-optional-dep"]
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
```json
|
|
418
|
-
{
|
|
419
|
-
"bundler": {
|
|
420
|
-
"external": ["fsevents", "some-optional-dep"]
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
Useful for:
|
|
426
|
-
- Platform-specific optional dependencies
|
|
427
|
-
- Packages that don't bundle well
|
|
428
|
-
- Reducing bundle size
|
|
429
|
-
|
|
430
|
-
## Platform Support
|
|
431
|
-
|
|
432
|
-
### Supported Targets
|
|
433
|
-
|
|
434
|
-
| Platform | Architectures | Example |
|
|
435
|
-
|----------|--------------|---------|
|
|
436
|
-
| Windows | x64, arm64 | `node24.11.0-win32-x64` |
|
|
437
|
-
| Linux | x64, arm64 | `node24.11.0-linux-x64` |
|
|
438
|
-
| macOS | x64, arm64 | `node24.11.0-darwin-arm64` |
|
|
439
|
-
|
|
440
|
-
### Node.js Versions
|
|
441
|
-
|
|
442
|
-
Works with Node.js 18.0.0 and above that support SEA.
|
|
443
|
-
|
|
444
|
-
## Troubleshooting
|
|
445
|
-
|
|
446
|
-
### Native modules not loading
|
|
447
|
-
|
|
448
|
-
If you see errors about missing `.node` files:
|
|
449
|
-
1. Check that the module was detected during build (look for "Native modules detected" in output)
|
|
450
|
-
2. Run with `--verbose` to see detailed bundling info
|
|
451
|
-
3. Ensure the module uses standard patterns (`bindings`, `node-gyp-build`, etc.)
|
|
452
|
-
|
|
453
|
-
### Build fails with signature removal error
|
|
454
|
-
|
|
455
|
-
Install the required tools:
|
|
456
|
-
- **Windows**: Install Windows SDK for `signtool.exe`
|
|
457
|
-
- **macOS**: Install Xcode Command Line Tools for `codesign`
|
|
458
|
-
|
|
459
|
-
### Cross-compilation issues
|
|
460
|
-
|
|
461
|
-
When building for a different platform than your current OS:
|
|
462
|
-
- Native module detection works cross-platform
|
|
463
|
-
- The bundled JavaScript is platform-agnostic
|
|
464
|
-
- Each target is built independently with the correct Node.js binary
|
|
465
|
-
|
|
466
302
|
## Contributing
|
|
467
303
|
|
|
468
304
|
Contributions welcome! Please open an issue or PR on [GitHub](https://github.com/MeirionHughes/seabox).
|
package/bin/seabox-rebuild.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import { execSync } from 'child_process';
|
|
|
8
8
|
import fs from 'fs';
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
|
+
import * as diag from '../lib/diagnostics.mjs';
|
|
11
12
|
|
|
12
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
14
|
const __dirname = path.dirname(__filename);
|
|
@@ -20,10 +21,10 @@ const __dirname = path.dirname(__filename);
|
|
|
20
21
|
* @param {boolean} verbose - Enable verbose logging
|
|
21
22
|
*/
|
|
22
23
|
function rebuildNativeModule(modulePath, platform, arch, verbose = false) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
24
|
+
diag.setVerbose(verbose);
|
|
25
|
+
|
|
26
|
+
diag.verbose(`Rebuilding native module: ${modulePath}`);
|
|
27
|
+
diag.verbose(`Target: ${platform}-${arch}`);
|
|
27
28
|
|
|
28
29
|
const packageJsonPath = path.join(modulePath, 'package.json');
|
|
29
30
|
if (!fs.existsSync(packageJsonPath)) {
|
|
@@ -36,9 +37,7 @@ function rebuildNativeModule(modulePath, platform, arch, verbose = false) {
|
|
|
36
37
|
// Check if module has native bindings
|
|
37
38
|
const hasBindingGyp = fs.existsSync(path.join(modulePath, 'binding.gyp'));
|
|
38
39
|
if (!hasBindingGyp && !pkg.gypfile) {
|
|
39
|
-
|
|
40
|
-
console.log(`Module ${moduleName} does not appear to have native bindings, skipping`);
|
|
41
|
-
}
|
|
40
|
+
diag.verbose(`Module ${moduleName} does not appear to have native bindings, skipping`);
|
|
42
41
|
return;
|
|
43
42
|
}
|
|
44
43
|
|
|
@@ -46,9 +45,7 @@ function rebuildNativeModule(modulePath, platform, arch, verbose = false) {
|
|
|
46
45
|
// Use node-gyp to rebuild for the target platform
|
|
47
46
|
const cmd = `npx node-gyp rebuild --target_platform=${platform} --target_arch=${arch}`;
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
console.log(`Running: ${cmd}`);
|
|
51
|
-
}
|
|
48
|
+
diag.verbose(`Running: ${cmd}`);
|
|
52
49
|
|
|
53
50
|
execSync(cmd, {
|
|
54
51
|
cwd: modulePath,
|
|
@@ -60,13 +57,9 @@ function rebuildNativeModule(modulePath, platform, arch, verbose = false) {
|
|
|
60
57
|
}
|
|
61
58
|
});
|
|
62
59
|
|
|
63
|
-
|
|
64
|
-
console.log(`ā Successfully rebuilt ${moduleName}`);
|
|
65
|
-
}
|
|
60
|
+
diag.verbose(`Successfully rebuilt ${moduleName}`);
|
|
66
61
|
} catch (error) {
|
|
67
|
-
|
|
68
|
-
console.error(`Failed to rebuild ${moduleName}:`, error.message);
|
|
69
|
-
}
|
|
62
|
+
diag.verbose(`Failed to rebuild ${moduleName}: ${error.message}`);
|
|
70
63
|
throw error;
|
|
71
64
|
}
|
|
72
65
|
}
|
|
@@ -76,7 +69,7 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
76
69
|
const args = process.argv.slice(2);
|
|
77
70
|
|
|
78
71
|
if (args.length < 3) {
|
|
79
|
-
|
|
72
|
+
diag.error('Usage: seabox-rebuild <module-path> <platform> <arch> [--verbose]');
|
|
80
73
|
process.exit(1);
|
|
81
74
|
}
|
|
82
75
|
|
|
@@ -87,7 +80,7 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
87
80
|
rebuildNativeModule(modulePath, platform, arch, verbose);
|
|
88
81
|
process.exit(0);
|
|
89
82
|
} catch (error) {
|
|
90
|
-
|
|
83
|
+
diag.error(`Rebuild failed: ${error.message}`);
|
|
91
84
|
process.exit(1);
|
|
92
85
|
}
|
|
93
86
|
}
|
package/bin/seabox.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import path from 'path';
|
|
|
11
11
|
import fs from 'fs';
|
|
12
12
|
import { fileURLToPath } from 'url';
|
|
13
13
|
import Module from 'module';
|
|
14
|
+
import * as diag from '../lib/diagnostics.mjs';
|
|
14
15
|
|
|
15
16
|
const __filename = fileURLToPath(import.meta.url);
|
|
16
17
|
const __dirname = path.dirname(__filename);
|
|
@@ -26,12 +27,15 @@ const commands = {
|
|
|
26
27
|
const config = loadConfig(configPath, projectRoot);
|
|
27
28
|
|
|
28
29
|
if (!config) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
diag.error('No configuration found');
|
|
31
|
+
diag.separator();
|
|
32
|
+
diag.info('Seabox looks for configuration in this order:');
|
|
33
|
+
diag.numberedItem(1, '--config <path> (command line argument)');
|
|
34
|
+
diag.numberedItem(2, 'seabox.config.json (in current directory)');
|
|
35
|
+
diag.numberedItem(3, '"seabox" field in package.json');
|
|
36
|
+
diag.separator();
|
|
37
|
+
diag.info('To get started, run: npx seabox init');
|
|
38
|
+
diag.separator();
|
|
35
39
|
commands.help();
|
|
36
40
|
process.exit(1);
|
|
37
41
|
}
|
|
@@ -61,7 +65,7 @@ const commands = {
|
|
|
61
65
|
const configPath = path.join(process.cwd(), 'seabox.config.json');
|
|
62
66
|
|
|
63
67
|
if (fs.existsSync(configPath)) {
|
|
64
|
-
|
|
68
|
+
diag.error('seabox.config.json already exists');
|
|
65
69
|
process.exit(1);
|
|
66
70
|
}
|
|
67
71
|
|
|
@@ -71,27 +75,34 @@ const commands = {
|
|
|
71
75
|
|
|
72
76
|
fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2) + '\n', 'utf8');
|
|
73
77
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
diag.success('Created seabox.config.json', 0);
|
|
79
|
+
diag.separator();
|
|
80
|
+
diag.info('Next steps:');
|
|
81
|
+
diag.numberedItem(1, 'Edit seabox.config.json to configure your build');
|
|
82
|
+
diag.numberedItem(2, 'Run: npx seabox build');
|
|
83
|
+
diag.separator();
|
|
78
84
|
},
|
|
79
85
|
|
|
80
86
|
help: () => {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
87
|
+
diag.info('Seabox v2 - Node.js Single Executable Application Builder');
|
|
88
|
+
diag.separator();
|
|
89
|
+
diag.info('Usage: seabox [command] [options]');
|
|
90
|
+
diag.separator();
|
|
91
|
+
diag.info('Commands:');
|
|
92
|
+
diag.info(' build Build executable(s) for configured targets (default)');
|
|
93
|
+
diag.info(' init Create a default seabox.config.json');
|
|
94
|
+
diag.separator();
|
|
95
|
+
diag.info('Build Options:');
|
|
96
|
+
diag.info(' --config Path to config file (default: seabox.config.json)');
|
|
97
|
+
diag.info(' --verbose Enable verbose logging');
|
|
98
|
+
diag.info(' --debug Keep temporary build files');
|
|
99
|
+
diag.separator();
|
|
100
|
+
diag.info('Examples:');
|
|
101
|
+
diag.info(' seabox init');
|
|
102
|
+
diag.info(' seabox build');
|
|
103
|
+
diag.info(' seabox --verbose # Same as: seabox build --verbose');
|
|
104
|
+
diag.info(' seabox build --verbose');
|
|
105
|
+
diag.separator();
|
|
95
106
|
}
|
|
96
107
|
};
|
|
97
108
|
|
|
@@ -119,14 +130,15 @@ async function main() {
|
|
|
119
130
|
try {
|
|
120
131
|
await commands[command](commandArgs);
|
|
121
132
|
} catch (error) {
|
|
122
|
-
|
|
133
|
+
diag.error(error.message);
|
|
123
134
|
if (args.includes('--verbose')) {
|
|
124
135
|
console.error(error.stack);
|
|
125
136
|
}
|
|
126
137
|
process.exit(1);
|
|
127
138
|
}
|
|
128
139
|
} else {
|
|
129
|
-
|
|
140
|
+
diag.error(`Unknown command: ${command}`);
|
|
141
|
+
diag.separator();
|
|
130
142
|
commands.help();
|
|
131
143
|
process.exit(1);
|
|
132
144
|
}
|
package/lib/blob.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import fs from 'fs';
|
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import { execFile } from 'child_process';
|
|
9
9
|
import { promisify } from 'util';
|
|
10
|
+
import * as diag from './diagnostics.mjs';
|
|
10
11
|
|
|
11
12
|
const execFileAsync = promisify(execFile);
|
|
12
13
|
|
|
@@ -96,7 +97,7 @@ export function writeSeaConfigJson(seaConfig, outputPath, assets, tempDir) {
|
|
|
96
97
|
export async function generateBlob(seaConfigPath, nodeBinary = process.execPath) {
|
|
97
98
|
try {
|
|
98
99
|
await execFileAsync(nodeBinary, ['--experimental-sea-config', seaConfigPath]);
|
|
99
|
-
|
|
100
|
+
diag.verbose('SEA blob generated successfully', 2);
|
|
100
101
|
} catch (error) {
|
|
101
102
|
throw new Error(`Failed to generate SEA blob: ${error.message}`);
|
|
102
103
|
}
|
package/lib/build.mjs
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { loadConfig } from './config.mjs';
|
|
7
7
|
import { MultiTargetBuilder } from './multi-target-builder.mjs';
|
|
8
|
+
import * as diag from './diagnostics.mjs';
|
|
8
9
|
import fs from 'fs';
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -49,17 +50,18 @@ export async function build(options = {}) {
|
|
|
49
50
|
const results = await builder.buildAll();
|
|
50
51
|
|
|
51
52
|
// Display results
|
|
52
|
-
|
|
53
|
+
diag.separator();
|
|
54
|
+
diag.info('Output files:');
|
|
53
55
|
for (const result of results) {
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
console.log(` ${result.target}: ${result.path} (${sizeMB} MB)`);
|
|
56
|
+
const size = diag.formatSize(fs.statSync(result.path).size);
|
|
57
|
+
diag.info(` ${result.target}: ${result.path} (${size})`);
|
|
57
58
|
}
|
|
58
|
-
|
|
59
|
+
diag.separator();
|
|
59
60
|
|
|
60
61
|
return results;
|
|
61
62
|
} catch (error) {
|
|
62
|
-
|
|
63
|
+
diag.separator();
|
|
64
|
+
diag.error(`Build failed: ${error.message}`);
|
|
63
65
|
if (verbose || process.argv.includes('--verbose')) {
|
|
64
66
|
console.error(error.stack);
|
|
65
67
|
}
|
package/lib/config.mjs
CHANGED
|
@@ -34,6 +34,7 @@ import path from 'path';
|
|
|
34
34
|
* @property {boolean} [useSnapshot] - Enable V8 snapshot
|
|
35
35
|
* @property {boolean} [useCodeCache] - Enable V8 code cache
|
|
36
36
|
* @property {string} [cacheLocation] - Cache directory for extracted binaries
|
|
37
|
+
* @property {string} [sign] - Path to signing script (.mjs/.cjs) that exports a function(config) => Promise<void>
|
|
37
38
|
* @property {boolean} [verbose] - Enable verbose logging
|
|
38
39
|
*/
|
|
39
40
|
|
|
@@ -89,11 +90,19 @@ export function loadConfig(configPath, projectRoot = process.cwd()) {
|
|
|
89
90
|
* @returns {SeaboxConfig}
|
|
90
91
|
*/
|
|
91
92
|
export function normalizeConfig(pkgConfig, pkg) {
|
|
93
|
+
// Helper to normalize assets to array
|
|
94
|
+
const normalizeAssets = (assets) => {
|
|
95
|
+
if (!assets) return [];
|
|
96
|
+
if (Array.isArray(assets)) return assets;
|
|
97
|
+
if (typeof assets === 'string') return [assets];
|
|
98
|
+
return [];
|
|
99
|
+
};
|
|
100
|
+
|
|
92
101
|
// If already in outputs format, return as-is
|
|
93
102
|
if (pkgConfig.outputs) {
|
|
94
103
|
return {
|
|
95
104
|
...pkgConfig,
|
|
96
|
-
assets: pkgConfig.assets
|
|
105
|
+
assets: normalizeAssets(pkgConfig.assets),
|
|
97
106
|
bundler: pkgConfig.bundler || { external: [] },
|
|
98
107
|
_packageName: pkg.name,
|
|
99
108
|
_packageVersion: pkg.version
|
|
@@ -112,7 +121,7 @@ export function normalizeConfig(pkgConfig, pkg) {
|
|
|
112
121
|
return {
|
|
113
122
|
entry: pkgConfig.entry,
|
|
114
123
|
outputs: outputs,
|
|
115
|
-
assets: pkgConfig.assets
|
|
124
|
+
assets: normalizeAssets(pkgConfig.assets),
|
|
116
125
|
bundler: {
|
|
117
126
|
external: pkgConfig.external || []
|
|
118
127
|
},
|