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.
@@ -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
+ }
@@ -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
- console.log(`✓ Using cached Node binary: ${cachedBinary}`);
148
+ diag.verbose(`Using cached Node binary: ${cachedBinary}`, 1);
148
149
  return cachedBinary;
149
150
  }
150
151
 
151
- console.log(`Downloading Node.js v${nodeVersion} for ${platform}-${arch}...`);
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
- console.log(`✓ Downloaded: ${archivePath}`);
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
- console.log(`✓ Extracted Node binary: ${binaryPath}`);
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
- const args = [
55
- outputPath,
56
- sentinel,
57
- blobPath,
58
- '--sentinel-fuse', sentinelFuse
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
- // Use cmd.exe on Windows to run npx
69
- const isWindows = process.platform === 'win32';
70
- const command = isWindows ? 'cmd.exe' : 'npx';
71
- const cmdArgs = isWindows
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
- const { stdout, stderr } = await execFileAsync(command, cmdArgs);
77
- if (stdout && verbose) console.log(stdout);
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
- if (verbose) {
94
- console.log('\nApplying rcedit to modify executable resources...');
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
- // Dynamic import for rcedit (it's CommonJS)
99
- const rcedit = (await import('rcedit')).default;
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
- if (verbose) {
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
  }