1111import { existsSync , promises as fs } from 'node:fs'
1212import path from 'node:path'
1313
14- import { safeDelete , safeMkdir } from '@socketsecurity/lib/fs'
14+ import { safeMkdir } from '@socketsecurity/lib/fs'
1515import { normalizePath } from '@socketsecurity/lib/paths/normalize'
1616import { spawn } from '@socketsecurity/lib/spawn'
1717
@@ -51,19 +51,20 @@ import { SOCKET_CLI_SEA_BUILD_DIR } from '../constants/paths.mjs'
5151 */
5252export async function generateSeaConfig ( entryPoint , outputPath ) {
5353 const outputName = path . basename ( outputPath , path . extname ( outputPath ) )
54+ const configDir = path . dirname ( outputPath )
5455 const configPath = normalizePath (
55- path . join ( path . dirname ( outputPath ) , `sea-config-${ outputName } .json` ) ,
56- )
57- const blobPath = normalizePath (
58- path . join ( path . dirname ( outputPath ) , `sea-blob-${ outputName } .blob` ) ,
56+ path . join ( configDir , `sea-config-${ outputName } .json` ) ,
5957 )
58+ // Use relative paths in sea-config.json (binject requires relative paths).
59+ const blobPathRelative = `sea-blob-${ outputName } .blob`
60+ const mainPathRelative = path . relative ( configDir , entryPoint )
6061
6162 const config = {
6263 // No assets to minimize size.
6364 assets : { } ,
6465 disableExperimentalSEAWarning : true ,
65- main : entryPoint ,
66- output : blobPath ,
66+ main : mainPathRelative ,
67+ output : blobPathRelative ,
6768 // Enable code cache for ~13% faster startup (~22ms improvement).
6869 // Pre-compiles JavaScript code during build time for instant execution.
6970 useCodeCache : true ,
@@ -97,7 +98,7 @@ export async function generateSeaConfig(entryPoint, outputPath) {
9798// which is critical for useCodeCache support (code cache is version-specific).
9899//
99100// This eliminates the Node.js version mismatch issue where we were using the host
100- // Node.js (e.g., v25.5.0) to generate blobs for node-smol targets (e.g., v24.10.0) .
101+ // Node.js to generate blobs for node-smol targets with different Node.js versions .
101102//
102103// See injectSeaBlob() below for the config-based blob generation implementation.
103104
@@ -109,9 +110,8 @@ export async function generateSeaConfig(entryPoint, outputPath) {
109110 * Inject SEA blob and optional VFS assets into a Node.js binary using binject.
110111 *
111112 * This function performs the core SEA binary build step by:
112- * 1. Generating an update-config.json for embedded update checking (binject --update-config).
113- * 2. Invoking binject to inject the SEA blob into the Node.js binary.
114- * 3. Optionally embedding security tools via VFS compression (binject --vfs).
113+ * 1. Invoking binject to inject the SEA blob into the Node.js binary.
114+ * 2. Optionally embedding security tools via VFS compression (binject --vfs).
115115 *
116116 * Config-Based Blob Generation:
117117 * Instead of pre-generating the SEA blob with `node --experimental-sea-config`, binject
@@ -123,11 +123,6 @@ export async function generateSeaConfig(entryPoint, outputPath) {
123123 * tools into the binary. This achieves ~70% compression compared to Node.js SEA assets.
124124 * If vfsTarGz is omitted, --vfs-compat mode is used (no actual VFS bundling).
125125 *
126- * Update Config Embedding:
127- * The function generates an update-config.json that node-smol's C stub uses for built-in
128- * update checking. This enables SEA binaries to check GitHub releases and notify users of
129- * available updates without needing TypeScript-based update checking.
130- *
131126 * @param {string } nodeBinary - Path to the node-smol binary to inject into.
132127 * @param {string } configPath - Path to the sea-config.json file for config-based blob generation.
133128 * @param {string } outputPath - Path to the output SEA binary (may be same as nodeBinary).
@@ -215,72 +210,32 @@ export async function injectSeaBlob(
215210 env [ 'SOCKET_DLX_DIR' ] = uniqueCacheDir
216211 }
217212
218- // Generate update-config.json for embedded update checking.
219- const updateConfigPath = normalizePath (
220- path . join ( path . dirname ( configPath ) , 'update-config.json' ) ,
221- )
222- const updateConfig = {
223- binname : 'socket' ,
224- command : 'self-update' ,
225- interval : 86_400_000 ,
226- notify_interval : 86_400_000 ,
227- prompt : false ,
228- prompt_default : 'n' ,
229- skip_env : 'SOCKET_CLI_SKIP_UPDATE_CHECK' ,
230- tag : 'socket-cli-*' ,
231- url : 'https://api.github.com/repos/SocketDev/socket-cli/releases' ,
213+ // Inject SEA blob into Node binary using binject.
214+ const args = [
215+ 'inject' ,
216+ '--executable' ,
217+ nodeBinary ,
218+ '--output' ,
219+ outputPath ,
220+ '--sea' ,
221+ configPath ,
222+ ]
223+
224+ // Add VFS if provided (compressed tar.gz), otherwise use vfs-compat mode.
225+ if ( vfsTarGz && existsSync ( vfsTarGz ) ) {
226+ args . push ( '--vfs' , vfsTarGz )
227+ } else {
228+ args . push ( '--vfs-compat' )
232229 }
233- await fs . writeFile ( updateConfigPath , JSON . stringify ( updateConfig , null , 2 ) )
234230
235- try {
236- // Inject SEA blob into Node binary using binject.
237- //
238- // Config-Based Blob Generation:
239- // When --sea points to a .json file (sea-config.json), binject reads the config
240- // and generates the blob automatically. This is more efficient than pre-generating
241- // with `node --experimental-sea-config`.
242- //
243- // VFS Compression:
244- // If vfsTarGz is provided, we use --vfs to embed compressed security tools.
245- // binject decompresses the tar.gz and injects the files into the binary's VFS.
246- // This achieves ~70% compression (460 MB → 140 MB for security tools).
247- //
248- // Without VFS (vfs-compat mode):
249- // If vfsTarGz is omitted, --vfs-compat mode is used, which injects only the SEA
250- // blob without any additional VFS data. This is useful for minimal CLI-only builds.
251- const args = [
252- 'inject' ,
253- '--executable' ,
254- nodeBinary ,
255- '--output' ,
256- outputPath ,
257- '--sea' ,
258- configPath ,
259- ]
231+ const result = await spawn ( binjectPath , args , { env, stdio : 'inherit' } )
260232
261- // Add VFS if provided (compressed tar.gz), otherwise use vfs-compat mode.
262- if ( vfsTarGz && existsSync ( vfsTarGz ) ) {
263- args . push ( '--vfs' , vfsTarGz )
264- } else {
265- args . push ( '--vfs-compat' )
266- }
267-
268- args . push ( '--update-config' , updateConfigPath )
269-
270- const result = await spawn ( binjectPath , args , { env, stdio : 'inherit' } )
271-
272- if (
273- result &&
274- typeof result === 'object' &&
275- 'exitCode' in result &&
276- result . exitCode !== 0
277- ) {
278- throw new Error ( `binject failed with exit code ${ result . exitCode } ` )
279- }
280- } finally {
281- // Clean up update config file (keep in debug mode for troubleshooting).
282- if ( ! process . env [ 'DEBUG' ] ) {
283- await safeDelete ( updateConfigPath ) . catch ( ( ) => { } )
284- }
233+ if (
234+ result &&
235+ typeof result === 'object' &&
236+ 'exitCode' in result &&
237+ result . exitCode !== 0
238+ ) {
239+ throw new Error ( `binject failed with exit code ${ result . exitCode } ` )
285240 }
286241}
0 commit comments