Skip to content

Commit c15caf7

Browse files
committed
refactor(dlx): extract defineToolSpawn factory; migrate 7 spawn-* wrappers
Every per-tool wrapper in dlx/spawn-* used to declare the same triple by hand: - spawn{Tool}Dlx — npm-CLI mode - spawn{Tool}Vfs — SEA mode - spawn{Tool} — auto-dispatcher Add `define-tool-spawn.mts` with three small helpers: - defineGitHubReleaseSpawn — full Dlx flow for tools that ship strictly via GitHub releases (resolveTool() returns 'github-release') - defineVfsSpawn — wraps spawnToolVfs() with the VFS bundle name - defineAutoDispatch — the canonical isSeaBinary()+areExternalToolsAvailable() branch shared by every tool And a top-level `defineToolSpawn` that emits the full triple in one call for the simple GitHub-release case. Migrations: - trufflehog, trivy, opengrep — pure GitHub release; collapse to one defineToolSpawn call each (81 → 22 LOC each). - synp — pure npm dlx; keep bespoke Dlx, use defineVfsSpawn + defineAutoDispatch for the rest (62 → 47 LOC). - cdxgen — local override + dlx; keep bespoke Dlx, use shared Vfs + auto-dispatch (99 → 84 LOC). - sfw — local override + machine-mode + dlx; keep bespoke Dlx, use shared Vfs + auto-dispatch (131 → 113 LOC). - socket-patch — three-way (local / GitHub release / legacy npm); keep bespoke Dlx, use shared Vfs + auto-dispatch (120 → 103 LOC). coana stays bespoke — its return shape (CResult<string>) and signature (extra orgSlug arg) don't fit the shared contract. LOC: 655 → 545 (-110 net) across the 7 migrated tools + the new helper. All 72 dlx unit tests still pass.
1 parent 5e64921 commit c15caf7

8 files changed

Lines changed: 217 additions & 323 deletions

File tree

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/**
2+
* Factory for "tool spawner" functions in the dlx/spawn-* family.
3+
*
4+
* Every per-tool wrapper exposes the same triple:
5+
* - spawn{Tool}Dlx — npm-CLI mode (download / local override / GitHub release)
6+
* - spawn{Tool}Vfs — SEA mode (extract from VFS bundle)
7+
* - spawn{Tool} — auto-dispatch between the two based on isSeaBinary()
8+
*
9+
* The auto-dispatch and the GitHub-release flow are identical across the
10+
* pure-binary tools (trufflehog, trivy, opengrep). This factory encapsulates
11+
* both so per-tool files can declare just `name + resolver` and get the rest.
12+
*
13+
* Hybrid tools that need local-path overrides or extra wiring (cdxgen, sfw,
14+
* socket-patch) keep their bespoke Dlx implementations and only call
15+
* `defineAutoDispatch` for the auto-dispatcher.
16+
*/
17+
18+
import { spawn } from '@socketsecurity/lib/spawn'
19+
20+
import { downloadGitHubReleaseBinary, spawnToolVfs } from './spawn.mts'
21+
import { areExternalToolsAvailable } from './vfs-extract.mjs'
22+
import { isSeaBinary } from '../sea/detect.mts'
23+
24+
import type { DlxOptions, DlxSpawnResult } from './spawn.mts'
25+
import type { BinaryResolution } from './resolve-binary.mts'
26+
import type { StdioOptions } from 'node:child_process'
27+
import type { SpawnExtra } from '@socketsecurity/lib/spawn'
28+
29+
/**
30+
* Argument shape for every spawn function the factory emits.
31+
*/
32+
export type ToolSpawnFn = (
33+
args: string[] | readonly string[],
34+
options?: DlxOptions | undefined,
35+
spawnExtra?: SpawnExtra | undefined,
36+
) => Promise<DlxSpawnResult>
37+
38+
/**
39+
* Build the standard auto-dispatcher: in SEA mode use VFS, otherwise use Dlx.
40+
*
41+
* Used by every tool wrapper in the dlx/spawn-* family.
42+
*/
43+
export function defineAutoDispatch(opts: {
44+
vfs: ToolSpawnFn
45+
dlx: ToolSpawnFn
46+
}): ToolSpawnFn {
47+
const { dlx, vfs } = opts
48+
return async (args, options, spawnExtra) => {
49+
if (isSeaBinary() && areExternalToolsAvailable()) {
50+
return await vfs(args, options, spawnExtra)
51+
}
52+
return await dlx(args, options, spawnExtra)
53+
}
54+
}
55+
56+
/**
57+
* Build the standard SEA-mode VFS spawner for a tool.
58+
*
59+
* The VFS name (e.g. 'trufflehog') is the directory key under the SEA bundle.
60+
*/
61+
export function defineVfsSpawn(vfsName: string): ToolSpawnFn {
62+
return async (args, options, spawnExtra) => {
63+
return await spawnToolVfs(vfsName, args, options, spawnExtra)
64+
}
65+
}
66+
67+
/**
68+
* Build a npm-CLI-mode spawner for a tool that ships strictly via GitHub
69+
* releases (trufflehog, trivy, opengrep). Throws a clearly-attributed
70+
* resolver-contract error if the resolver returns a non-github-release type.
71+
*/
72+
export function defineGitHubReleaseSpawn(opts: {
73+
toolName: string
74+
resolve: () => BinaryResolution
75+
}): ToolSpawnFn {
76+
const { resolve, toolName } = opts
77+
return async (args, options, spawnExtra) => {
78+
const resolution = resolve()
79+
80+
if (resolution.type !== 'github-release') {
81+
throw new Error(
82+
`internal: resolve${capitalize(toolName)} returned resolution.type="${resolution.type}" (expected "github-release"); this is a resolver contract bug — re-run with --debug and report the output`,
83+
)
84+
}
85+
86+
const { env: spawnEnv, ...dlxOptions } = {
87+
__proto__: null,
88+
...options,
89+
} as DlxOptions
90+
91+
const binaryPath = await downloadGitHubReleaseBinary(resolution.details)
92+
93+
const spawnPromise = spawn(binaryPath, args, {
94+
...dlxOptions,
95+
env: {
96+
...process.env,
97+
...spawnEnv,
98+
},
99+
stdio: (spawnExtra?.['stdio'] as StdioOptions | undefined) ?? 'inherit',
100+
})
101+
102+
return { spawnPromise }
103+
}
104+
}
105+
106+
/**
107+
* Build the full spawn-* triple for a pure-GitHub-release tool.
108+
*
109+
* Returns `{ Dlx, Vfs, auto }` where `auto` is the public spawnFoo()
110+
* dispatcher and Dlx/Vfs are the underlying spawners.
111+
*/
112+
export function defineToolSpawn(opts: {
113+
toolName: string
114+
vfsName: string
115+
resolve: () => BinaryResolution
116+
}): {
117+
Dlx: ToolSpawnFn
118+
Vfs: ToolSpawnFn
119+
auto: ToolSpawnFn
120+
} {
121+
const Dlx = defineGitHubReleaseSpawn({
122+
toolName: opts.toolName,
123+
resolve: opts.resolve,
124+
})
125+
const Vfs = defineVfsSpawn(opts.vfsName)
126+
const auto = defineAutoDispatch({ vfs: Vfs, dlx: Dlx })
127+
return { Dlx, Vfs, auto }
128+
}
129+
130+
function capitalize(s: string): string {
131+
return s.length ? s[0]!.toUpperCase() + s.slice(1) : s
132+
}

packages/cli/src/utils/dlx/spawn-cdxgen.mts

Lines changed: 14 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,21 @@
44
* - spawnCdxgenDlx: local override > Socket dlx download.
55
* - spawnCdxgenVfs: extract from SEA bundle, then exec.
66
* - spawnCdxgen: auto-detect SEA vs npm-CLI mode and dispatch.
7+
*
8+
* The local-override path is bespoke (cdxgen is a JS file when local), so
9+
* Dlx stays hand-rolled here. Vfs + auto-dispatch use the shared helpers
10+
* from define-tool-spawn.
711
*/
812

913
import { detectExecutableType } from '@socketsecurity/lib/dlx/detect'
1014
import { spawn } from '@socketsecurity/lib/spawn'
1115

12-
import { spawnDlx, spawnToolVfs } from './spawn.mts'
16+
import {
17+
defineAutoDispatch,
18+
defineVfsSpawn,
19+
} from './define-tool-spawn.mts'
20+
import { spawnDlx } from './spawn.mts'
1321
import { resolveCdxgen } from './resolve-binary.mjs'
14-
import { areExternalToolsAvailable } from './vfs-extract.mjs'
15-
import { isSeaBinary } from '../sea/detect.mts'
1622

1723
import type { DlxOptions, DlxSpawnResult } from './spawn.mts'
1824
import type { StdioOptions } from 'node:child_process'
@@ -70,29 +76,9 @@ export async function spawnCdxgenDlx(
7076
)
7177
}
7278

73-
/**
74-
* Helper to spawn cdxgen from VFS.
75-
* Used when running in SEA mode.
76-
*/
77-
export async function spawnCdxgenVfs(
78-
args: string[] | readonly string[],
79-
options?: DlxOptions | undefined,
80-
spawnExtra?: SpawnExtra | undefined,
81-
): Promise<DlxSpawnResult> {
82-
return await spawnToolVfs('cdxgen', args, options, spawnExtra)
83-
}
79+
export const spawnCdxgenVfs = defineVfsSpawn('cdxgen')
8480

85-
/**
86-
* Spawn cdxgen (CycloneDX generator).
87-
* Auto-detects SEA mode and uses appropriate spawn method.
88-
*/
89-
export async function spawnCdxgen(
90-
args: string[] | readonly string[],
91-
options?: DlxOptions | undefined,
92-
spawnExtra?: SpawnExtra | undefined,
93-
): Promise<DlxSpawnResult> {
94-
if (isSeaBinary() && areExternalToolsAvailable()) {
95-
return await spawnCdxgenVfs(args, options, spawnExtra)
96-
}
97-
return await spawnCdxgenDlx(args, options, spawnExtra)
98-
}
81+
export const spawnCdxgen = defineAutoDispatch({
82+
vfs: spawnCdxgenVfs,
83+
dlx: spawnCdxgenDlx,
84+
})

packages/cli/src/utils/dlx/spawn-opengrep.mts

Lines changed: 11 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -4,78 +4,19 @@
44
* - spawnOpengrepDlx: download from GitHub releases, then exec.
55
* - spawnOpengrepVfs: extract from SEA bundle, then exec.
66
* - spawnOpengrep: auto-detect SEA vs npm-CLI mode and dispatch.
7+
*
8+
* Defined via `defineToolSpawn`. See utils/dlx/define-tool-spawn.mts.
79
*/
810

9-
import { spawn } from '@socketsecurity/lib/spawn'
10-
11-
import {
12-
downloadGitHubReleaseBinary,
13-
spawnToolVfs,
14-
} from './spawn.mts'
11+
import { defineToolSpawn } from './define-tool-spawn.mts'
1512
import { resolveOpengrep } from './resolve-binary.mjs'
16-
import { areExternalToolsAvailable } from './vfs-extract.mjs'
17-
import { isSeaBinary } from '../sea/detect.mts'
18-
19-
import type { DlxOptions, DlxSpawnResult } from './spawn.mts'
20-
import type { StdioOptions } from 'node:child_process'
21-
import type { SpawnExtra } from '@socketsecurity/lib/spawn'
22-
23-
export async function spawnOpengrepDlx(
24-
args: string[] | readonly string[],
25-
options?: DlxOptions | undefined,
26-
spawnExtra?: SpawnExtra | undefined,
27-
): Promise<DlxSpawnResult> {
28-
const resolution = resolveOpengrep()
29-
30-
if (resolution.type !== 'github-release') {
31-
throw new Error(
32-
`internal: resolveOpengrep returned resolution.type="${resolution.type}" (expected "github-release"); this is a resolver contract bug — re-run with --debug and report the output`,
33-
)
34-
}
35-
36-
const { env: spawnEnv, ...dlxOptions } = {
37-
__proto__: null,
38-
...options,
39-
} as DlxOptions
40-
41-
const binaryPath = await downloadGitHubReleaseBinary(resolution.details)
4213

43-
const spawnPromise = spawn(binaryPath, args, {
44-
...dlxOptions,
45-
env: {
46-
...process.env,
47-
...spawnEnv,
48-
},
49-
stdio: (spawnExtra?.['stdio'] as StdioOptions | undefined) ?? 'inherit',
50-
})
14+
const triple = defineToolSpawn({
15+
toolName: 'opengrep',
16+
vfsName: 'opengrep',
17+
resolve: resolveOpengrep,
18+
})
5119

52-
return {
53-
spawnPromise,
54-
}
55-
}
56-
57-
/**
58-
* Spawn OpenGrep from VFS (SEA mode).
59-
*/
60-
export async function spawnOpengrepVfs(
61-
args: string[] | readonly string[],
62-
options?: DlxOptions | undefined,
63-
spawnExtra?: SpawnExtra | undefined,
64-
): Promise<DlxSpawnResult> {
65-
return await spawnToolVfs('opengrep', args, options, spawnExtra)
66-
}
67-
68-
/**
69-
* Spawn OpenGrep.
70-
* Auto-detects SEA mode and uses appropriate spawn method.
71-
*/
72-
export async function spawnOpengrep(
73-
args: string[] | readonly string[],
74-
options?: DlxOptions | undefined,
75-
spawnExtra?: SpawnExtra | undefined,
76-
): Promise<DlxSpawnResult> {
77-
if (isSeaBinary() && areExternalToolsAvailable()) {
78-
return await spawnOpengrepVfs(args, options, spawnExtra)
79-
}
80-
return await spawnOpengrepDlx(args, options, spawnExtra)
81-
}
20+
export const spawnOpengrepDlx = triple.Dlx
21+
export const spawnOpengrepVfs = triple.Vfs
22+
export const spawnOpengrep = triple.auto

packages/cli/src/utils/dlx/spawn-sfw.mts

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,19 @@
77
*
88
* sfw is a transparent proxy: args is [innerTool, innerSubcommand?, ...rest].
99
* Machine-mode flags forward to the inner tool so its stdout stays pipe-safe
10-
* under --json.
10+
* under --json. The Dlx flow stays bespoke (machine-mode + local-override
11+
* both apply); Vfs + auto-dispatch use the shared helpers.
1112
*/
1213

1314
import { detectExecutableType } from '@socketsecurity/lib/dlx/detect'
1415
import { spawn } from '@socketsecurity/lib/spawn'
1516

16-
import { spawnDlx, spawnToolVfs } from './spawn.mts'
17+
import {
18+
defineAutoDispatch,
19+
defineVfsSpawn,
20+
} from './define-tool-spawn.mts'
21+
import { spawnDlx } from './spawn.mts'
1722
import { resolveSfw } from './resolve-binary.mjs'
18-
import { areExternalToolsAvailable } from './vfs-extract.mjs'
19-
import { isSeaBinary } from '../sea/detect.mts'
2023
import {
2124
applyMachineModeIfActive,
2225
inferSubcommand,
@@ -102,29 +105,9 @@ export async function spawnSfwDlx(
102105
)
103106
}
104107

105-
/**
106-
* Helper to spawn Socket Firewall (sfw) from VFS.
107-
* Used when running in SEA mode.
108-
*/
109-
export async function spawnSfwVfs(
110-
args: string[] | readonly string[],
111-
options?: DlxOptions | undefined,
112-
spawnExtra?: SpawnExtra | undefined,
113-
): Promise<DlxSpawnResult> {
114-
return await spawnToolVfs('sfw', args, options, spawnExtra)
115-
}
108+
export const spawnSfwVfs = defineVfsSpawn('sfw')
116109

117-
/**
118-
* Spawn Socket Firewall (sfw).
119-
* Auto-detects SEA mode and uses appropriate spawn method.
120-
*/
121-
export async function spawnSfw(
122-
args: string[] | readonly string[],
123-
options?: DlxOptions | undefined,
124-
spawnExtra?: SpawnExtra | undefined,
125-
): Promise<DlxSpawnResult> {
126-
if (isSeaBinary() && areExternalToolsAvailable()) {
127-
return await spawnSfwVfs(args, options, spawnExtra)
128-
}
129-
return await spawnSfwDlx(args, options, spawnExtra)
130-
}
110+
export const spawnSfw = defineAutoDispatch({
111+
vfs: spawnSfwVfs,
112+
dlx: spawnSfwDlx,
113+
})

0 commit comments

Comments
 (0)