seabox 0.1.0-beta.4 ā 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 +60 -233
- 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 +19 -35
- 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 +2 -1
package/README.md
CHANGED
|
@@ -57,12 +57,12 @@ Create a `seabox.config.json` file in your project root:
|
|
|
57
57
|
| `outputs[].path` | `string` | Yes | Output directory for this target |
|
|
58
58
|
| `outputs[].target` | `string` | Yes | Build target (format: `nodeX.Y.Z-platform-arch`) |
|
|
59
59
|
| `outputs[].output` | `string` | Yes | Output filename |
|
|
60
|
-
| `outputs[].libraries` | `array` | No |
|
|
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
61
|
| `outputs[].rcedit` | `object` | No | Windows executable metadata (icon, version info) |
|
|
62
62
|
| `assets` | `array` | No | Glob patterns for assets to embed (merged with auto-detected assets) |
|
|
63
|
-
| `bundler` | `object` | No | Bundler options |
|
|
63
|
+
| `bundler` | `object` | No | Rolldown Bundler options |
|
|
64
64
|
| `bundler.external` | `array` | No | Modules to exclude from bundling |
|
|
65
|
-
| `bundler.plugins` | `array` | No | Additional
|
|
65
|
+
| `bundler.plugins` | `array` | No | Additional Rolldown plugins |
|
|
66
66
|
| `bundler.minify` | `boolean` | No | Minify bundled code |
|
|
67
67
|
| `bundler.sourcemap` | `boolean` | No | Generate source maps |
|
|
68
68
|
| `encryptAssets` | `boolean` | No | Enable asset encryption (default: false) |
|
|
@@ -70,6 +70,7 @@ Create a `seabox.config.json` file in your project root:
|
|
|
70
70
|
| `useSnapshot` | `boolean` | No | Enable V8 startup snapshots (default: true) |
|
|
71
71
|
| `useCodeCache` | `boolean` | No | Enable V8 code cache (default: false) |
|
|
72
72
|
| `cacheLocation` | `string` | No | Path for code cache storage |
|
|
73
|
+
| `sign` | `string` | No | Path to custom signing script (.mjs/.cjs) |
|
|
73
74
|
| `verbose` | `boolean` | No | Enable verbose logging (default: false) |
|
|
74
75
|
|
|
75
76
|
## Usage
|
|
@@ -127,9 +128,10 @@ await build({
|
|
|
127
128
|
|
|
128
129
|
seabox automates the entire SEA build process:
|
|
129
130
|
|
|
130
|
-
1. **Bundling** - Automatically bundles your app with
|
|
131
|
+
1. **Bundling** - Automatically bundles your app with Rolldown, detecting:
|
|
131
132
|
- Native module patterns (`bindings`, `node-gyp-build`, direct `.node` requires)
|
|
132
133
|
- Asset references via `path.join(__dirname, 'relative/path')`
|
|
134
|
+
- Additional
|
|
133
135
|
|
|
134
136
|
2. **Asset Collection** - Gathers assets from three sources:
|
|
135
137
|
- **Auto-detected**: Files referenced via `path.join(__dirname, ...)` patterns
|
|
@@ -163,11 +165,6 @@ const configPath = path.join(__dirname, '../config/app.json');
|
|
|
163
165
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
164
166
|
```
|
|
165
167
|
|
|
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
168
|
**Asset sources (merged and deduplicated):**
|
|
172
169
|
1. **Auto-detected** from code analysis during bundling
|
|
173
170
|
2. **Config globs** from `assets: ["./data/**/*", "./public/**/*"]`
|
|
@@ -187,28 +184,35 @@ seabox automatically handles native modules without any configuration:
|
|
|
187
184
|
- Native modules are extracted to a cache directory on first run
|
|
188
185
|
- Modules are integrity-checked with SHA-256 hashes
|
|
189
186
|
- Custom `require()` shim loads modules from cache
|
|
190
|
-
- Works transparently with packages like `better-sqlite3`, `sharp`, `canvas`, etc.
|
|
191
|
-
|
|
192
187
|
|
|
193
188
|
### Platform-Specific Libraries
|
|
194
189
|
|
|
195
|
-
Libraries that require filesystem access (like DLLs
|
|
190
|
+
Libraries that require filesystem access (like DLLs loaded via `dlopen`) can be included in two ways:
|
|
191
|
+
|
|
192
|
+
**1. Automatic Detection (Recommended)**
|
|
193
|
+
|
|
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
|
+
```
|
|
200
|
+
|
|
201
|
+
**2. Explicit Glob Patterns**
|
|
202
|
+
|
|
203
|
+
You can also explicitly specify library patterns in your config:
|
|
196
204
|
|
|
197
205
|
```json
|
|
198
206
|
{
|
|
199
207
|
"outputs": [
|
|
200
208
|
{
|
|
201
209
|
"target": "node24.11.0-win32-x64",
|
|
202
|
-
"libraries": ["
|
|
210
|
+
"libraries": ["lib/*.dll"] // Manually specify DLLs to include
|
|
203
211
|
}
|
|
204
212
|
]
|
|
205
213
|
}
|
|
206
214
|
```
|
|
207
215
|
|
|
208
|
-
**Defaults by platform:**
|
|
209
|
-
- **Windows**: `**/*.dll`
|
|
210
|
-
- **Linux**: `**/*.so`, `**/*.so.*`
|
|
211
|
-
- **macOS**: `**/*.dylib`
|
|
212
216
|
|
|
213
217
|
These files are extracted on first run (like `.node` files) since they need to be loaded from the filesystem.
|
|
214
218
|
|
|
@@ -219,6 +223,43 @@ Required before SEA injection. Platform-specific tools needed:
|
|
|
219
223
|
- **macOS**: `codesign` (included with Xcode)
|
|
220
224
|
- **Linux**: Not required
|
|
221
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
|
+
```
|
|
235
|
+
|
|
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
|
+
```
|
|
254
|
+
|
|
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
|
|
262
|
+
|
|
222
263
|
## Asset Encryption
|
|
223
264
|
|
|
224
265
|
seabox supports optional AES-256-GCM encryption of embedded assets to protect your application code and data:
|
|
@@ -238,231 +279,17 @@ seabox supports optional AES-256-GCM encryption of embedded assets to protect yo
|
|
|
238
279
|
1. **Build Time**: A random 256-bit encryption key is generated
|
|
239
280
|
2. **Asset Encryption**: Non-binary assets are encrypted using AES-256-GCM
|
|
240
281
|
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
|
|
282
|
+
4. **Key Obfuscation**: the bootstrap and key code are obfuscated
|
|
242
283
|
5. **Runtime Decryption**: Assets are transparently decrypted when accessed
|
|
243
284
|
|
|
244
285
|
### Considerations
|
|
245
286
|
|
|
246
287
|
- **Binary files** (`.node`, `.dll`, `.so`, `.dylib`) are **never encrypted** as they must be extracted as-is
|
|
247
288
|
- 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.
|
|
289
|
+
- **V8 snapshot includes the original source**, this is currently a limitation of Node's SEA tooling.
|
|
249
290
|
- Encryption provides **obfuscation**, not cryptographic security against determined attackers
|
|
250
291
|
- The bootloader code, that includes the encryption key, is obfuscated in the source embedded by Node's SEA
|
|
251
292
|
|
|
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
293
|
## Contributing
|
|
467
294
|
|
|
468
295
|
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
|
},
|