4848 * ├── package.json
4949 * └── node_modules/
5050 *
51- * PLANNED ENHANCEMENT: Runtime directory tree extraction
52- * Currently extracts only single binary files. Full directory tree extraction is a
53- * planned optimization documented in socket-btm/docs/vfs-configuration-plan.md .
51+ * VFS Extraction with Full Directory Support:
52+ * Uses process.smol.mount() API from node-smol to extract both single files and
53+ * complete directory trees with dependencies from the embedded VFS .
5454 *
55- * VFS APIs (getAsset, getAssetKeys) already exist in node-smol. When implemented:
56- * - Extract entire directory trees from VFS (not just binaries).
57- * - Use process.smol VFS APIs that support directory extraction.
58- * - Preserve file permissions and directory structure.
59- * - Handle node_modules/ dependencies at runtime.
55+ * For npm packages:
56+ * - Extracts entire package directory (node_modules/@package/name/)
57+ * - Includes all production dependencies and subdirectories
58+ * - Preserves file permissions and directory structure
6059 *
61- * Current workaround: Extract only the binary with a warning when dependencies are
62- * missing (see line 296-302). This limitation does not block current functionality.
60+ * For standalone binaries:
61+ * - Extracts individual binary file from VFS root
62+ *
63+ * See socket-btm/docs/vfs-runtime-api.md for full documentation.
6364 */
6465
6566import { createHash } from 'node:crypto'
6667import { existsSync , promises as fs } from 'node:fs'
67- import { createRequire } from 'node:module'
6868import { homedir } from 'node:os'
6969import path from 'node:path'
7070
@@ -76,22 +76,8 @@ import { normalizePath } from '@socketsecurity/lib/paths/normalize'
7676import { UPDATE_STORE_DIR } from '../../constants/paths.mts'
7777import { isSeaBinary } from '../sea/detect.mts'
7878
79- const require = createRequire ( import . meta. url )
80-
81- // Conditionally load node:sea module (Node.js 24+ only).
82- let getAsset : ( ( key : string ) => ArrayBuffer ) | undefined
83- try {
84- const sea = require ( 'node:sea' )
85- getAsset = sea . getAsset
86- } catch {
87- // node:sea not available (Node.js < 24 or not in SEA context).
88- }
89-
9079const logger = getDefaultLogger ( )
9180
92- // VFS asset path prefix for external tools.
93- const VFS_ASSET_PREFIX = 'external-tools'
94-
9581// External tool names bundled in VFS.
9682// Includes both npm packages and standalone binaries.
9783export const EXTERNAL_TOOLS = [
@@ -178,21 +164,18 @@ function getNodeSmolBasePath(): string {
178164 * Check if external tools are available in VFS.
179165 *
180166 * Returns true if:
181- * 1. Running in SEA mode with VFS assets
167+ * 1. Running in SEA mode with process.smol.mount available
182168 *
183169 * @returns True if external tools are available in VFS.
184170 */
185171export function areExternalToolsAvailable ( ) : boolean {
186- // Check if running in SEA mode with VFS assets.
187- if ( isSeaBinary ( ) && getAsset ) {
188- try {
189- // Check if at least sfw is available in VFS.
190- getAsset ( `${ VFS_ASSET_PREFIX } /sfw` )
191- return true
192- } catch {
193- debug ( 'notice' , 'SEA mode but VFS assets not available for external tools' )
194- return false
195- }
172+ const processWithSmol = process as unknown as {
173+ smol ?: { mount ?: ( vfsPath : string ) => string }
174+ }
175+
176+ // Check if running in SEA mode with process.smol.mount available.
177+ if ( isSeaBinary ( ) && processWithSmol . smol ?. mount ) {
178+ return true
196179 }
197180
198181 // Not in SEA mode - tools will be downloaded via dlx.
@@ -226,27 +209,26 @@ async function isNpmPackageExtracted(packagePath: string): Promise<boolean> {
226209}
227210
228211/**
229- * Extract a single external tool from VFS to node-smol's dlx directory.
212+ * Extract a single external tool from VFS to node-smol's dlx directory using process.smol.mount() .
230213 * Extracts to ~/.socket/_dlx/<node-smol-hash>/node_modules/{packageName}/bin/{binaryName}
231214 *
232- * Current implementation: Extracts only the binary file as a temporary solution.
233- *
234- * PLANNED ENHANCEMENT: Full directory tree extraction (see socket-btm/docs/vfs-configuration-plan.md)
235- * VFS APIs already exist in node-smol. When this optimization is implemented:
236- * 1. Check if binject auto-extracted the VFS to node-smol base directory.
237- * 2. If not, use VFS directory extraction APIs to extract full package trees.
238- * 3. Preserve file permissions and directory structure.
239- * 4. Handle symlinks properly (binLinks from Arborist).
240- *
241- * Current workaround: Extract only the binary with warning (see line 296-302).
242- * Full packages with dependencies will need directory VFS extraction to work correctly.
215+ * Implementation:
216+ * - npm packages: Uses process.smol.mount() to extract entire directory with dependencies
217+ * - Standalone binaries: Uses process.smol.mount() to extract single file
218+ * - Automatically handles file permissions and directory structure
219+ * - Supports caching to avoid re-extraction
243220 *
244221 * @param tool - Name of the tool to extract.
245222 * @returns Path to the extracted tool binary.
246223 */
247224async function extractTool ( tool : ExternalTool ) : Promise < string > {
248- if ( ! getAsset ) {
249- throw new Error ( 'getAsset not available - not in SEA mode' )
225+ // Check if process.smol.mount is available.
226+ const processWithSmol = process as unknown as {
227+ smol ?: { mount ?: ( vfsPath : string ) => string }
228+ }
229+
230+ if ( ! processWithSmol . smol ?. mount ) {
231+ throw new Error ( 'process.smol.mount not available - not in node-smol SEA mode' )
250232 }
251233
252234 const isPlatWin = process . platform === 'win32'
@@ -273,42 +255,35 @@ async function extractTool(tool: ExternalTool): Promise<string> {
273255 }
274256 }
275257
276- // Extract binary from VFS (works for both npm packages and standalone binaries).
277- const assetKey = `${ VFS_ASSET_PREFIX } /${ tool } `
278-
258+ // Extract from VFS using process.smol.mount().
279259 try {
280- const assetBuffer = getAsset ( assetKey )
260+ let extractedPath : string
281261
282- // For npm packages, extract to node_modules/ path.
283- // For standalone binaries, extract to direct path.
284- const toolPath = npmPath
285- ? normalizePath ( path . join ( nodeSmolBase , npmPath . binPath ) )
286- : normalizePath ( path . join ( nodeSmolBase , tool ) )
262+ if ( npmPath ) {
263+ // Extract entire npm package directory with dependencies.
264+ const vfsPackagePath = `/snapshot/node_modules/${ npmPath . packageName } `
265+ const packageDir = processWithSmol . smol . mount ( vfsPackagePath )
287266
288- const toolPathWithExt = isPlatWin ? ` ${ toolPath } .exe` : toolPath
267+ logger . info ( ` ✓ Extracted ${ tool } package with dependencies to ${ packageDir } ` )
289268
290- // Create directory for the binary.
291- const toolDir = path . dirname ( toolPathWithExt )
292- await safeMkdir ( toolDir )
269+ // Return path to binary within extracted package.
270+ const toolPath = normalizePath ( path . join ( nodeSmolBase , npmPath . binPath ) )
271+ extractedPath = isPlatWin ? `${ toolPath } .exe` : toolPath
272+ } else {
273+ // Extract standalone binary from VFS root.
274+ const vfsBinaryPath = `/snapshot/${ tool } `
275+ const binaryPath = processWithSmol . smol . mount ( vfsBinaryPath )
293276
294- // Write tool binary.
295- await fs . writeFile ( toolPathWithExt , Buffer . from ( assetBuffer ) )
277+ logger . info ( ` ✓ Extracted ${ tool } binary to ${ binaryPath } ` )
296278
297- // Make executable on Unix.
298- if ( ! isPlatWin ) {
299- await fs . chmod ( toolPathWithExt , 0o755 )
279+ extractedPath = isPlatWin ? `${ binaryPath } .exe` : binaryPath
300280 }
301281
302- logger . info ( ` ✓ Extracted ${ tool } binary to ${ toolPathWithExt } ` )
303-
304- // Warn for npm packages extracted without dependencies.
305- if ( npmPath ) {
306- logger . warn (
307- ` ⚠ ${ tool } extracted without dependencies - may not function correctly` ,
308- )
282+ if ( ! existsSync ( extractedPath ) ) {
283+ throw new Error ( `Extracted tool not found at ${ extractedPath } ` )
309284 }
310285
311- return toolPathWithExt
286+ return extractedPath
312287 } catch ( e ) {
313288 throw new Error ( `Failed to extract ${ tool } from VFS: ${ e } ` )
314289 }
@@ -347,7 +322,11 @@ export async function extractExternalTools(
347322 return null
348323 }
349324
350- if ( ! isSeaBinary ( ) || ! getAsset ) {
325+ const processWithSmol = process as unknown as {
326+ smol ?: { mount ?: ( vfsPath : string ) => string }
327+ }
328+
329+ if ( ! isSeaBinary ( ) || ! processWithSmol . smol ?. mount ) {
351330 debug ( 'notice' , 'Not running in SEA mode - cannot extract VFS tools' )
352331 return null
353332 }
0 commit comments