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
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* diagnostics.mjs
|
|
3
|
+
* Centralized console output formatting for CLI tooling.
|
|
4
|
+
* Provides consistent formatting, indentation, and styling.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Output levels
|
|
9
|
+
*/
|
|
10
|
+
export const Level = {
|
|
11
|
+
ERROR: 'error',
|
|
12
|
+
WARN: 'warn',
|
|
13
|
+
INFO: 'info',
|
|
14
|
+
SUCCESS: 'success',
|
|
15
|
+
VERBOSE: 'verbose'
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Formatting utilities
|
|
20
|
+
*/
|
|
21
|
+
const colors = {
|
|
22
|
+
reset: '\x1b[0m',
|
|
23
|
+
red: '\x1b[31m',
|
|
24
|
+
green: '\x1b[32m',
|
|
25
|
+
yellow: '\x1b[33m',
|
|
26
|
+
blue: '\x1b[34m',
|
|
27
|
+
gray: '\x1b[90m'
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function withColor(text, color) {
|
|
31
|
+
return `${color}${text}${colors.reset}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Global verbose flag
|
|
36
|
+
*/
|
|
37
|
+
let verboseEnabled = false;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Enable or disable verbose output
|
|
41
|
+
* @param {boolean} enabled
|
|
42
|
+
*/
|
|
43
|
+
export function setVerbose(enabled) {
|
|
44
|
+
verboseEnabled = enabled;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Core logging function
|
|
49
|
+
* @param {string} message
|
|
50
|
+
* @param {string} prefix
|
|
51
|
+
* @param {number} indent
|
|
52
|
+
*/
|
|
53
|
+
function log(message, prefix = '', indent = 0) {
|
|
54
|
+
const indentation = ' '.repeat(indent);
|
|
55
|
+
console.log(`${indentation}${prefix}${message}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Print section header
|
|
60
|
+
* @param {string} title
|
|
61
|
+
*/
|
|
62
|
+
export function header(title) {
|
|
63
|
+
console.log(`\n${title}`);
|
|
64
|
+
console.log('='.repeat(40));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Print sub-header
|
|
69
|
+
* @param {string} title
|
|
70
|
+
*/
|
|
71
|
+
export function subheader(title) {
|
|
72
|
+
console.log(`\n${title}`);
|
|
73
|
+
console.log('-'.repeat(40));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Print step with number
|
|
78
|
+
* @param {number} step
|
|
79
|
+
* @param {number} total
|
|
80
|
+
* @param {string} message
|
|
81
|
+
*/
|
|
82
|
+
export function step(step, total, message) {
|
|
83
|
+
log(`[${step}/${total}] ${message}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Print build step
|
|
88
|
+
* @param {number} buildNum
|
|
89
|
+
* @param {number} stepNum
|
|
90
|
+
* @param {string} message
|
|
91
|
+
*/
|
|
92
|
+
export function buildStep(buildNum, stepNum, message) {
|
|
93
|
+
log(`[${buildNum}.${stepNum}] ${message}`, '', 1);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Print success message
|
|
98
|
+
* @param {string} message
|
|
99
|
+
* @param {number} indent
|
|
100
|
+
*/
|
|
101
|
+
export function success(message, indent = 1) {
|
|
102
|
+
log(withColor(`[✓] ${message}`, colors.green), '', indent);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Print info message
|
|
107
|
+
* @param {string} message
|
|
108
|
+
* @param {number} indent
|
|
109
|
+
*/
|
|
110
|
+
export function info(message, indent = 0) {
|
|
111
|
+
log(message, '', indent);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Print warning
|
|
116
|
+
* @param {string} message
|
|
117
|
+
* @param {number} indent
|
|
118
|
+
*/
|
|
119
|
+
export function warn(message, indent = 1) {
|
|
120
|
+
log(withColor(`[!] ${message}`, colors.yellow), '', indent);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Print error
|
|
125
|
+
* @param {string} message
|
|
126
|
+
* @param {number} indent
|
|
127
|
+
*/
|
|
128
|
+
export function error(message, indent = 0) {
|
|
129
|
+
console.error(`${' '.repeat(indent)}${withColor(`[X] ${message}`, colors.red)}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Print verbose message (only if verbose enabled)
|
|
134
|
+
* @param {string} message
|
|
135
|
+
* @param {number} indent
|
|
136
|
+
*/
|
|
137
|
+
export function verbose(message, indent = 2) {
|
|
138
|
+
if (verboseEnabled) {
|
|
139
|
+
log(withColor(message, colors.gray), '', indent);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Print list item
|
|
145
|
+
* @param {string} message
|
|
146
|
+
* @param {number} indent
|
|
147
|
+
*/
|
|
148
|
+
export function listItem(message, indent = 1) {
|
|
149
|
+
log(`- ${message}`, '', indent);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Print numbered list item
|
|
154
|
+
* @param {number} num
|
|
155
|
+
* @param {string} message
|
|
156
|
+
* @param {number} indent
|
|
157
|
+
*/
|
|
158
|
+
export function numberedItem(num, message, indent = 1) {
|
|
159
|
+
log(`${num}. ${message}`, '', indent);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Print section separator
|
|
164
|
+
*/
|
|
165
|
+
export function separator() {
|
|
166
|
+
console.log('');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Print final summary
|
|
171
|
+
* @param {string} message
|
|
172
|
+
*/
|
|
173
|
+
export function summary(message) {
|
|
174
|
+
console.log('');
|
|
175
|
+
console.log('='.repeat(40));
|
|
176
|
+
log(withColor(message, colors.green));
|
|
177
|
+
console.log('='.repeat(40));
|
|
178
|
+
console.log('');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Format file size in human-readable format
|
|
183
|
+
* @param {number} bytes
|
|
184
|
+
* @returns {string}
|
|
185
|
+
*/
|
|
186
|
+
export function formatSize(bytes) {
|
|
187
|
+
const mb = bytes / 1024 / 1024;
|
|
188
|
+
if (mb >= 1) {
|
|
189
|
+
return `${mb.toFixed(2)} MB`;
|
|
190
|
+
}
|
|
191
|
+
const kb = bytes / 1024;
|
|
192
|
+
return `${kb.toFixed(2)} KB`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Format target string
|
|
197
|
+
* @param {string} platform
|
|
198
|
+
* @param {string} arch
|
|
199
|
+
* @returns {string}
|
|
200
|
+
*/
|
|
201
|
+
export function formatTarget(platform, arch) {
|
|
202
|
+
return `${platform}-${arch}`;
|
|
203
|
+
}
|
package/lib/fetch-node.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import { pipeline } from 'stream';
|
|
|
10
10
|
import { promisify } from 'util';
|
|
11
11
|
import AdmZip from 'adm-zip';
|
|
12
12
|
import tar from 'tar';
|
|
13
|
+
import * as diag from './diagnostics.mjs';
|
|
13
14
|
|
|
14
15
|
const pipelineAsync = promisify(pipeline);
|
|
15
16
|
|
|
@@ -144,23 +145,23 @@ export async function fetchNodeBinary(nodeVersion, platform, arch, cacheDir) {
|
|
|
144
145
|
|
|
145
146
|
// Check cache
|
|
146
147
|
if (fs.existsSync(cachedBinary)) {
|
|
147
|
-
|
|
148
|
+
diag.verbose(`Using cached Node binary: ${cachedBinary}`, 1);
|
|
148
149
|
return cachedBinary;
|
|
149
150
|
}
|
|
150
151
|
|
|
151
|
-
|
|
152
|
+
diag.verbose(`Downloading Node.js v${nodeVersion} for ${platform}-${arch}...`, 1);
|
|
152
153
|
const url = getNodeDownloadUrl(nodeVersion, platform, arch);
|
|
153
154
|
const archiveName = path.basename(url);
|
|
154
155
|
const archivePath = path.join(cacheDir, archiveName);
|
|
155
156
|
|
|
156
157
|
await downloadFile(url, archivePath);
|
|
157
|
-
|
|
158
|
+
diag.verbose(`Downloaded: ${archivePath}`, 1);
|
|
158
159
|
|
|
159
160
|
const extractDir = path.join(cacheDir, `${nodeVersion}-${platform}-${arch}`);
|
|
160
161
|
fs.mkdirSync(extractDir, { recursive: true });
|
|
161
162
|
|
|
162
163
|
const binaryPath = await extractNodeBinary(archivePath, extractDir, platform);
|
|
163
|
-
|
|
164
|
+
diag.verbose(`Extracted Node binary: ${binaryPath}`, 1);
|
|
164
165
|
|
|
165
166
|
// Clean up archive
|
|
166
167
|
fs.unlinkSync(archivePath);
|
package/lib/inject.mjs
CHANGED
|
@@ -5,19 +5,16 @@
|
|
|
5
5
|
|
|
6
6
|
import fs from 'fs';
|
|
7
7
|
import path from 'path';
|
|
8
|
-
import { execFile } from 'child_process';
|
|
9
|
-
import { promisify } from 'util';
|
|
10
8
|
import Module from 'module';
|
|
11
9
|
import { fileURLToPath } from 'url';
|
|
10
|
+
import * as diag from './diagnostics.mjs';
|
|
12
11
|
|
|
13
12
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
13
|
const __dirname = path.dirname(__filename);
|
|
15
14
|
|
|
16
15
|
// Import unsign using require since it's CommonJS
|
|
17
16
|
const require = Module.createRequire(import.meta.url);
|
|
18
|
-
const { removeSignature } = require('./unsign.cjs');
|
|
19
|
-
|
|
20
|
-
const execFileAsync = promisify(execFile);
|
|
17
|
+
const { removeSignature, setVerbose: setUnsignVerbose } = require('./unsign.cjs');
|
|
21
18
|
|
|
22
19
|
/**
|
|
23
20
|
* Inject a SEA blob into a Node.js binary using postject.
|
|
@@ -40,6 +37,7 @@ export async function injectBlob(nodeBinaryPath, blobPath, outputPath, platform,
|
|
|
40
37
|
fs.copyFileSync(nodeBinaryPath, outputPath);
|
|
41
38
|
|
|
42
39
|
// Remove existing signature before postject injection
|
|
40
|
+
setUnsignVerbose(verbose);
|
|
43
41
|
await removeSignature(outputPath, platform);
|
|
44
42
|
|
|
45
43
|
// Apply rcedit changes (Windows only, before postject)
|
|
@@ -51,32 +49,22 @@ export async function injectBlob(nodeBinaryPath, blobPath, outputPath, platform,
|
|
|
51
49
|
const sentinel = 'NODE_SEA_BLOB';
|
|
52
50
|
const sentinelFuse = 'NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2';
|
|
53
51
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
// Platform-specific postject options
|
|
62
|
-
if (platform === 'darwin') {
|
|
63
|
-
args.push('--macho-segment-name', 'NODE_SEA');
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
console.log(`Injecting SEA blob into: ${outputPath}`);
|
|
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);
|
|
67
59
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
? ['/c', 'npx', 'postject', ...args]
|
|
73
|
-
: ['postject', ...args];
|
|
60
|
+
const injectOptions = {
|
|
61
|
+
sentinelFuse,
|
|
62
|
+
machoSegmentName: platform === 'darwin' ? 'NODE_SEA' : undefined
|
|
63
|
+
};
|
|
74
64
|
|
|
75
65
|
try {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (stderr && verbose) console.error(stderr);
|
|
79
|
-
console.log('✓ SEA blob injected successfully');
|
|
66
|
+
await postject.inject(outputPath, sentinel, blobData, injectOptions);
|
|
67
|
+
diag.verbose('SEA blob injected successfully', 2);
|
|
80
68
|
} catch (error) {
|
|
81
69
|
throw new Error(`Postject injection failed: ${error.message}`);
|
|
82
70
|
}
|
|
@@ -90,19 +78,26 @@ export async function injectBlob(nodeBinaryPath, blobPath, outputPath, platform,
|
|
|
90
78
|
* @returns {Promise<void>}
|
|
91
79
|
*/
|
|
92
80
|
async function applyRcedit(exePath, options, verbose) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
console.log('Options:', JSON.stringify(options, null, 2));
|
|
96
|
-
}
|
|
81
|
+
diag.verbose('Applying rcedit to modify executable resources...', 2);
|
|
82
|
+
diag.verbose(`Options: ${JSON.stringify(options, null, 2)}`, 2);
|
|
97
83
|
|
|
98
|
-
//
|
|
99
|
-
|
|
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
|
+
}
|
|
100
97
|
|
|
101
98
|
try {
|
|
102
99
|
await rcedit(exePath, options);
|
|
103
|
-
|
|
104
|
-
console.log('✓ rcedit applied successfully');
|
|
105
|
-
}
|
|
100
|
+
diag.verbose('rcedit applied successfully', 2);
|
|
106
101
|
} catch (error) {
|
|
107
102
|
throw new Error(`rcedit failed: ${error.message}`);
|
|
108
103
|
}
|