Skip to content

Commit 06e5260

Browse files
committed
feat(cli): use process.smol.mount() for full VFS directory extraction
- Use process.smol.mount() API for extracting tools from VFS with full dependencies - Extract complete npm package directories (node_modules/@package/name/) - Remove manual getAsset() + fs.writeFile() workaround - Automatically extract all production dependencies and subdirectories - Preserve file permissions and directory structure - No longer warn about missing dependencies (they're now extracted) For npm packages: - Mount entire package directory: /snapshot/node_modules/@cyclonedx/cdxgen - Includes all production dependencies from Arborist - Supports cdxgen, coana, socket-patch, synp For standalone binaries: - Mount single binary file: /snapshot/sfw - Handles GitHub release binaries node-smol now supports full directory tree extraction with the mount() API, eliminating the previous limitation documented in TODOs. See socket-btm/docs/vfs-runtime-api.md for full documentation.
1 parent 894d021 commit 06e5260

File tree

1 file changed

+56
-77
lines changed

1 file changed

+56
-77
lines changed

packages/cli/src/utils/dlx/vfs-extract.mts

Lines changed: 56 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -48,23 +48,23 @@
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

6566
import { createHash } from 'node:crypto'
6667
import { existsSync, promises as fs } from 'node:fs'
67-
import { createRequire } from 'node:module'
6868
import { homedir } from 'node:os'
6969
import path from 'node:path'
7070

@@ -76,22 +76,8 @@ import { normalizePath } from '@socketsecurity/lib/paths/normalize'
7676
import { UPDATE_STORE_DIR } from '../../constants/paths.mts'
7777
import { 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-
9079
const 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.
9783
export 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
*/
185171
export 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
*/
247224
async 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

Comments
 (0)