seabox 0.1.1 → 0.2.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/.mocharc.json +6 -6
- package/LICENSE.MD +21 -21
- package/README.md +310 -310
- package/bin/seabox-rebuild.mjs +88 -88
- package/bin/seabox.mjs +150 -147
- package/lib/blob.mjs +104 -104
- package/lib/bootstrap.cjs +756 -753
- package/lib/build-cache.mjs +199 -199
- package/lib/build.mjs +77 -77
- package/lib/config.mjs +243 -243
- package/lib/crypto-assets.mjs +125 -125
- package/lib/diagnostics.mjs +203 -203
- package/lib/entry-bundler.mjs +64 -64
- package/lib/fetch-node.mjs +172 -172
- package/lib/index.mjs +26 -26
- package/lib/inject.mjs +106 -106
- package/lib/manifest.mjs +100 -100
- package/lib/multi-target-builder.mjs +697 -705
- package/lib/native-scanner.mjs +203 -203
- package/lib/obfuscate.mjs +51 -51
- package/lib/require-shim.mjs +113 -113
- package/lib/rolldown-bundler.mjs +411 -411
- package/lib/unsign.cjs +197 -169
- package/package.json +61 -61
package/lib/inject.mjs
CHANGED
|
@@ -1,106 +1,106 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* inject.mjs
|
|
3
|
-
* Inject SEA blob into Node binary using postject.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import fs from 'fs';
|
|
7
|
-
import path from 'path';
|
|
8
|
-
import Module from 'module';
|
|
9
|
-
import { fileURLToPath } from 'url';
|
|
10
|
-
import * as diag from './diagnostics.mjs';
|
|
11
|
-
|
|
12
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
-
const __dirname = path.dirname(__filename);
|
|
14
|
-
|
|
15
|
-
// Import unsign using require since it's CommonJS
|
|
16
|
-
const require = Module.createRequire(import.meta.url);
|
|
17
|
-
const { removeSignature, setVerbose: setUnsignVerbose } = require('./unsign.cjs');
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Inject a SEA blob into a Node.js binary using postject.
|
|
21
|
-
* @param {string} nodeBinaryPath - Path to the source Node binary
|
|
22
|
-
* @param {string} blobPath - Path to the SEA blob file
|
|
23
|
-
* @param {string} outputPath - Path for the output executable
|
|
24
|
-
* @param {string} platform - Target platform (win32, linux, darwin)
|
|
25
|
-
* @param {boolean} verbose - Enable verbose logging
|
|
26
|
-
* @param {Object} [rceditOptions] - Optional rcedit configuration for Windows executables
|
|
27
|
-
* @returns {Promise<void>}
|
|
28
|
-
*/
|
|
29
|
-
export async function injectBlob(nodeBinaryPath, blobPath, outputPath, platform, verbose, rceditOptions) {
|
|
30
|
-
// Ensure output directory exists
|
|
31
|
-
const outputDir = path.dirname(outputPath);
|
|
32
|
-
if (!fs.existsSync(outputDir)) {
|
|
33
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Copy node binary to output location
|
|
37
|
-
fs.copyFileSync(nodeBinaryPath, outputPath);
|
|
38
|
-
|
|
39
|
-
// Remove existing signature before postject injection
|
|
40
|
-
setUnsignVerbose(verbose);
|
|
41
|
-
await removeSignature(outputPath, platform);
|
|
42
|
-
|
|
43
|
-
// Apply rcedit changes (Windows only, before postject)
|
|
44
|
-
if (platform === 'win32' && rceditOptions && typeof rceditOptions === 'object') {
|
|
45
|
-
await applyRcedit(outputPath, rceditOptions, verbose);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Prepare postject command
|
|
49
|
-
const sentinel = 'NODE_SEA_BLOB';
|
|
50
|
-
const sentinelFuse = 'NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2';
|
|
51
|
-
|
|
52
|
-
diag.verbose(`Injecting SEA blob into: ${outputPath}`, 1);
|
|
53
|
-
|
|
54
|
-
// Use postject programmatically
|
|
55
|
-
const postject = (await import('postject')).default;
|
|
56
|
-
|
|
57
|
-
// Read blob data as buffer
|
|
58
|
-
const blobData = fs.readFileSync(blobPath);
|
|
59
|
-
|
|
60
|
-
const injectOptions = {
|
|
61
|
-
sentinelFuse,
|
|
62
|
-
machoSegmentName: platform === 'darwin' ? 'NODE_SEA' : undefined
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
try {
|
|
66
|
-
await postject.inject(outputPath, sentinel, blobData, injectOptions);
|
|
67
|
-
diag.verbose('SEA blob injected successfully', 2);
|
|
68
|
-
} catch (error) {
|
|
69
|
-
throw new Error(`Postject injection failed: ${error.message}`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Apply rcedit to modify Windows executable resources.
|
|
75
|
-
* @param {string} exePath - Path to the executable
|
|
76
|
-
* @param {Object} options - rcedit options (icon, version-string, file-version, product-version, etc.)
|
|
77
|
-
* @param {boolean} verbose - Enable verbose logging
|
|
78
|
-
* @returns {Promise<void>}
|
|
79
|
-
*/
|
|
80
|
-
async function applyRcedit(exePath, options, verbose) {
|
|
81
|
-
diag.verbose('Applying rcedit to modify executable resources...', 2);
|
|
82
|
-
diag.verbose(`Options: ${JSON.stringify(options, null, 2)}`, 2);
|
|
83
|
-
|
|
84
|
-
// Try to import rcedit - it's a peer dependency
|
|
85
|
-
let rcedit;
|
|
86
|
-
try {
|
|
87
|
-
rcedit = (await import('rcedit')).default;
|
|
88
|
-
} catch (error) {
|
|
89
|
-
if (error.code === 'ERR_MODULE_NOT_FOUND') {
|
|
90
|
-
throw new Error(
|
|
91
|
-
'rcedit is required for Windows executable metadata but not installed. ' +
|
|
92
|
-
'Install it with: npm install rcedit'
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
throw error;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
try {
|
|
99
|
-
await rcedit(exePath, options);
|
|
100
|
-
diag.verbose('rcedit applied successfully', 2);
|
|
101
|
-
} catch (error) {
|
|
102
|
-
throw new Error(`rcedit failed: ${error.message}`);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export { applyRcedit };
|
|
1
|
+
/**
|
|
2
|
+
* inject.mjs
|
|
3
|
+
* Inject SEA blob into Node binary using postject.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import Module from 'module';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
import * as diag from './diagnostics.mjs';
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = path.dirname(__filename);
|
|
14
|
+
|
|
15
|
+
// Import unsign using require since it's CommonJS
|
|
16
|
+
const require = Module.createRequire(import.meta.url);
|
|
17
|
+
const { removeSignature, setVerbose: setUnsignVerbose } = require('./unsign.cjs');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Inject a SEA blob into a Node.js binary using postject.
|
|
21
|
+
* @param {string} nodeBinaryPath - Path to the source Node binary
|
|
22
|
+
* @param {string} blobPath - Path to the SEA blob file
|
|
23
|
+
* @param {string} outputPath - Path for the output executable
|
|
24
|
+
* @param {string} platform - Target platform (win32, linux, darwin)
|
|
25
|
+
* @param {boolean} verbose - Enable verbose logging
|
|
26
|
+
* @param {Object} [rceditOptions] - Optional rcedit configuration for Windows executables
|
|
27
|
+
* @returns {Promise<void>}
|
|
28
|
+
*/
|
|
29
|
+
export async function injectBlob(nodeBinaryPath, blobPath, outputPath, platform, verbose, rceditOptions) {
|
|
30
|
+
// Ensure output directory exists
|
|
31
|
+
const outputDir = path.dirname(outputPath);
|
|
32
|
+
if (!fs.existsSync(outputDir)) {
|
|
33
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Copy node binary to output location
|
|
37
|
+
fs.copyFileSync(nodeBinaryPath, outputPath);
|
|
38
|
+
|
|
39
|
+
// Remove existing signature before postject injection
|
|
40
|
+
setUnsignVerbose(verbose);
|
|
41
|
+
await removeSignature(outputPath, platform);
|
|
42
|
+
|
|
43
|
+
// Apply rcedit changes (Windows only, before postject)
|
|
44
|
+
if (platform === 'win32' && rceditOptions && typeof rceditOptions === 'object') {
|
|
45
|
+
await applyRcedit(outputPath, rceditOptions, verbose);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Prepare postject command
|
|
49
|
+
const sentinel = 'NODE_SEA_BLOB';
|
|
50
|
+
const sentinelFuse = 'NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2';
|
|
51
|
+
|
|
52
|
+
diag.verbose(`Injecting SEA blob into: ${outputPath}`, 1);
|
|
53
|
+
|
|
54
|
+
// Use postject programmatically
|
|
55
|
+
const postject = (await import('postject')).default;
|
|
56
|
+
|
|
57
|
+
// Read blob data as buffer
|
|
58
|
+
const blobData = fs.readFileSync(blobPath);
|
|
59
|
+
|
|
60
|
+
const injectOptions = {
|
|
61
|
+
sentinelFuse,
|
|
62
|
+
machoSegmentName: platform === 'darwin' ? 'NODE_SEA' : undefined
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
await postject.inject(outputPath, sentinel, blobData, injectOptions);
|
|
67
|
+
diag.verbose('SEA blob injected successfully', 2);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
throw new Error(`Postject injection failed: ${error.message}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Apply rcedit to modify Windows executable resources.
|
|
75
|
+
* @param {string} exePath - Path to the executable
|
|
76
|
+
* @param {Object} options - rcedit options (icon, version-string, file-version, product-version, etc.)
|
|
77
|
+
* @param {boolean} verbose - Enable verbose logging
|
|
78
|
+
* @returns {Promise<void>}
|
|
79
|
+
*/
|
|
80
|
+
async function applyRcedit(exePath, options, verbose) {
|
|
81
|
+
diag.verbose('Applying rcedit to modify executable resources...', 2);
|
|
82
|
+
diag.verbose(`Options: ${JSON.stringify(options, null, 2)}`, 2);
|
|
83
|
+
|
|
84
|
+
// Try to import rcedit - it's a peer dependency
|
|
85
|
+
let rcedit;
|
|
86
|
+
try {
|
|
87
|
+
rcedit = (await import('rcedit')).default;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
if (error.code === 'ERR_MODULE_NOT_FOUND') {
|
|
90
|
+
throw new Error(
|
|
91
|
+
'rcedit is required for Windows executable metadata but not installed. ' +
|
|
92
|
+
'Install it with: npm install rcedit'
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
await rcedit(exePath, options);
|
|
100
|
+
diag.verbose('rcedit applied successfully', 2);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
throw new Error(`rcedit failed: ${error.message}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export { applyRcedit };
|
package/lib/manifest.mjs
CHANGED
|
@@ -1,100 +1,100 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* manifest.mjs
|
|
3
|
-
* Generate runtime manifest with asset metadata and extraction rules.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import path from 'path';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @typedef {Object} BinaryManifestEntry
|
|
10
|
-
* @property {string} assetKey - Key in the SEA blob
|
|
11
|
-
* @property {string} fileName - Original filename
|
|
12
|
-
* @property {string} platform - Target platform (win32, linux, darwin, *)
|
|
13
|
-
* @property {string} arch - Target architecture (x64, arm64, *)
|
|
14
|
-
* @property {number} order - Extraction order priority (lower = earlier)
|
|
15
|
-
* @property {string} hash - SHA-256 hash for integrity check
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @typedef {Object} RuntimeManifest
|
|
20
|
-
* @property {string} appName - Application name
|
|
21
|
-
* @property {string} appVersion - Application version
|
|
22
|
-
* @property {string} platform - Target platform
|
|
23
|
-
* @property {string} arch - Target architecture
|
|
24
|
-
* @property {BinaryManifestEntry[]} binaries - Binary extraction rules
|
|
25
|
-
* @property {string[]} allAssetKeys - All embedded asset keys
|
|
26
|
-
* @property {string} [cacheLocation] - Configured cache location (optional)
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Generate a runtime manifest from scanned assets.
|
|
31
|
-
* @param {Array} assets - All scanned assets
|
|
32
|
-
* @param {Object} config - SEA configuration
|
|
33
|
-
* @param {string} targetPlatform - Target platform (win32, linux, darwin)
|
|
34
|
-
* @param {string} targetArch - Target architecture (x64, arm64)
|
|
35
|
-
* @returns {RuntimeManifest}
|
|
36
|
-
*/
|
|
37
|
-
export function generateManifest(assets, config, targetPlatform, targetArch) {
|
|
38
|
-
const binaries = assets
|
|
39
|
-
.filter(a => a.isBinary)
|
|
40
|
-
.map((asset, index) => {
|
|
41
|
-
const fileName = path.basename(asset.sourcePath);
|
|
42
|
-
return {
|
|
43
|
-
assetKey: asset.assetKey,
|
|
44
|
-
fileName,
|
|
45
|
-
platform: targetPlatform,
|
|
46
|
-
arch: targetArch,
|
|
47
|
-
order: inferExtractionOrder(fileName, index),
|
|
48
|
-
hash: asset.hash
|
|
49
|
-
};
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
const manifest = {
|
|
53
|
-
appName: config._packageName || 'app',
|
|
54
|
-
appVersion: config._packageVersion || '1.0.0',
|
|
55
|
-
platform: targetPlatform,
|
|
56
|
-
arch: targetArch,
|
|
57
|
-
binaries,
|
|
58
|
-
allAssetKeys: assets.map(a => a.assetKey)
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
// Include cacheLocation if configured
|
|
62
|
-
if (config.cacheLocation) {
|
|
63
|
-
manifest.cacheLocation = config.cacheLocation;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return manifest;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Infer extraction order based on file type.
|
|
71
|
-
* Libraries (.dll, .so, .dylib) should extract before .node addons.
|
|
72
|
-
* @param {string} fileName
|
|
73
|
-
* @param {number} fallbackIndex - Fallback order if heuristic doesn't apply
|
|
74
|
-
* @returns {number}
|
|
75
|
-
*/
|
|
76
|
-
function inferExtractionOrder(fileName, fallbackIndex) {
|
|
77
|
-
const ext = path.extname(fileName).toLowerCase();
|
|
78
|
-
|
|
79
|
-
// Extract shared libraries first
|
|
80
|
-
if (['.dll', '.so', '.dylib'].includes(ext)) {
|
|
81
|
-
return 10;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Then native addons
|
|
85
|
-
if (ext === '.node') {
|
|
86
|
-
return 20;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Fallback for other binaries
|
|
90
|
-
return 100 + fallbackIndex;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Serialize manifest to JSON string for embedding.
|
|
95
|
-
* @param {RuntimeManifest} manifest
|
|
96
|
-
* @returns {string}
|
|
97
|
-
*/
|
|
98
|
-
export function serializeManifest(manifest) {
|
|
99
|
-
return JSON.stringify(manifest, null, 2);
|
|
100
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* manifest.mjs
|
|
3
|
+
* Generate runtime manifest with asset metadata and extraction rules.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import path from 'path';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {Object} BinaryManifestEntry
|
|
10
|
+
* @property {string} assetKey - Key in the SEA blob
|
|
11
|
+
* @property {string} fileName - Original filename
|
|
12
|
+
* @property {string} platform - Target platform (win32, linux, darwin, *)
|
|
13
|
+
* @property {string} arch - Target architecture (x64, arm64, *)
|
|
14
|
+
* @property {number} order - Extraction order priority (lower = earlier)
|
|
15
|
+
* @property {string} hash - SHA-256 hash for integrity check
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {Object} RuntimeManifest
|
|
20
|
+
* @property {string} appName - Application name
|
|
21
|
+
* @property {string} appVersion - Application version
|
|
22
|
+
* @property {string} platform - Target platform
|
|
23
|
+
* @property {string} arch - Target architecture
|
|
24
|
+
* @property {BinaryManifestEntry[]} binaries - Binary extraction rules
|
|
25
|
+
* @property {string[]} allAssetKeys - All embedded asset keys
|
|
26
|
+
* @property {string} [cacheLocation] - Configured cache location (optional)
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Generate a runtime manifest from scanned assets.
|
|
31
|
+
* @param {Array} assets - All scanned assets
|
|
32
|
+
* @param {Object} config - SEA configuration
|
|
33
|
+
* @param {string} targetPlatform - Target platform (win32, linux, darwin)
|
|
34
|
+
* @param {string} targetArch - Target architecture (x64, arm64)
|
|
35
|
+
* @returns {RuntimeManifest}
|
|
36
|
+
*/
|
|
37
|
+
export function generateManifest(assets, config, targetPlatform, targetArch) {
|
|
38
|
+
const binaries = assets
|
|
39
|
+
.filter(a => a.isBinary)
|
|
40
|
+
.map((asset, index) => {
|
|
41
|
+
const fileName = path.basename(asset.sourcePath);
|
|
42
|
+
return {
|
|
43
|
+
assetKey: asset.assetKey,
|
|
44
|
+
fileName,
|
|
45
|
+
platform: targetPlatform,
|
|
46
|
+
arch: targetArch,
|
|
47
|
+
order: inferExtractionOrder(fileName, index),
|
|
48
|
+
hash: asset.hash
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const manifest = {
|
|
53
|
+
appName: config._packageName || 'app',
|
|
54
|
+
appVersion: config._packageVersion || '1.0.0',
|
|
55
|
+
platform: targetPlatform,
|
|
56
|
+
arch: targetArch,
|
|
57
|
+
binaries,
|
|
58
|
+
allAssetKeys: assets.map(a => a.assetKey)
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Include cacheLocation if configured
|
|
62
|
+
if (config.cacheLocation) {
|
|
63
|
+
manifest.cacheLocation = config.cacheLocation;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return manifest;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Infer extraction order based on file type.
|
|
71
|
+
* Libraries (.dll, .so, .dylib) should extract before .node addons.
|
|
72
|
+
* @param {string} fileName
|
|
73
|
+
* @param {number} fallbackIndex - Fallback order if heuristic doesn't apply
|
|
74
|
+
* @returns {number}
|
|
75
|
+
*/
|
|
76
|
+
function inferExtractionOrder(fileName, fallbackIndex) {
|
|
77
|
+
const ext = path.extname(fileName).toLowerCase();
|
|
78
|
+
|
|
79
|
+
// Extract shared libraries first
|
|
80
|
+
if (['.dll', '.so', '.dylib'].includes(ext)) {
|
|
81
|
+
return 10;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Then native addons
|
|
85
|
+
if (ext === '.node') {
|
|
86
|
+
return 20;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Fallback for other binaries
|
|
90
|
+
return 100 + fallbackIndex;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Serialize manifest to JSON string for embedding.
|
|
95
|
+
* @param {RuntimeManifest} manifest
|
|
96
|
+
* @returns {string}
|
|
97
|
+
*/
|
|
98
|
+
export function serializeManifest(manifest) {
|
|
99
|
+
return JSON.stringify(manifest, null, 2);
|
|
100
|
+
}
|