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 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 | Glob patterns for shared libraries (DLLs/SOs) requiring filesystem extraction (defaults: `**/*.dll` for Windows, `**/*.so*` for Linux, `**/*.dylib` for macOS) |
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 Rollup plugins |
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 Rollup, detecting:
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 that are loaded via `dlopen`) can be specified with glob patterns:
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": ["**/*.dll"] // Auto-extracted at runtime
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, but not removed
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).
@@ -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
- if (verbose) {
24
- console.log(`Rebuilding native module: ${modulePath}`);
25
- console.log(`Target: ${platform}-${arch}`);
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
- if (verbose) {
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
- if (verbose) {
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
- if (verbose) {
64
- console.log(`āœ“ Successfully rebuilt ${moduleName}`);
65
- }
60
+ diag.verbose(`Successfully rebuilt ${moduleName}`);
66
61
  } catch (error) {
67
- if (verbose) {
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
- console.error('Usage: seabox-rebuild <module-path> <platform> <arch> [--verbose]');
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
- console.error('Rebuild failed:', error.message);
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
- console.log('āŒ No configuration found\n');
30
- console.log('Seabox looks for configuration in this order:');
31
- console.log(' 1. --config <path> (command line argument)');
32
- console.log(' 2. seabox.config.json (in current directory)');
33
- console.log(' 3. "seabox" field in package.json\n');
34
- console.log('To get started, run: npx seabox init\n');
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
- console.error('āŒ Error: seabox.config.json already exists');
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
- console.log('āœ… Created seabox.config.json');
75
- console.log('\nšŸ“ Next steps:');
76
- console.log(' 1. Edit seabox.config.json to configure your build');
77
- console.log(' 2. Run: npx seabox build\n');
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
- console.log('Seabox v2 - Node.js Single Executable Application Builder\n');
82
- console.log('Usage: seabox [command] [options]\n');
83
- console.log('Commands:');
84
- console.log(' build Build executable(s) for configured targets (default)');
85
- console.log(' init Create a default seabox.config.json\n');
86
- console.log('Build Options:');
87
- console.log(' --config Path to config file (default: seabox.config.json)');
88
- console.log(' --verbose Enable verbose logging');
89
- console.log(' --debug Keep temporary build files\n');
90
- console.log('Examples:');
91
- console.log(' seabox init');
92
- console.log(' seabox build');
93
- console.log(' seabox --verbose # Same as: seabox build --verbose');
94
- console.log(' seabox build --verbose\n');
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
- console.error('Error:', error.message);
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
- console.error(`Unknown command: ${command}\n`);
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
- console.log('āœ“ SEA blob generated successfully');
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
- console.log('šŸ“¦ Output files:');
53
+ diag.separator();
54
+ diag.info('Output files:');
53
55
  for (const result of results) {
54
- const stats = fs.statSync(result.path);
55
- const sizeMB = (stats.size / 1024 / 1024).toFixed(2);
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
- console.log('');
59
+ diag.separator();
59
60
 
60
61
  return results;
61
62
  } catch (error) {
62
- console.error('\nāŒ Build failed:', error.message);
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
  },