1- import { runPatch } from '@socketsecurity/socket-patch/run'
1+ import { spawnSync } from 'node:child_process'
2+ import { existsSync } from 'node:fs'
3+ import path from 'node:path'
24
35import constants from '../../constants.mts'
46
57import type { CliCommandContext } from '../../utils/meow-with-subcommands.mts'
68
79export const CMD_NAME = 'patch'
810
9- const description = 'Manage CVE patches for dependencies'
11+ const description =
12+ 'Apply, manage, and rollback Socket security patches for vulnerable dependencies'
1013
1114const hidden = false
1215
@@ -16,42 +19,80 @@ export const cmdPatch = {
1619 run,
1720}
1821
22+ // Resolve the path to the socket-patch binary.
23+ // The @socketsecurity /socket-patch package registers a bin entry that pnpm
24+ // links into node_modules/.bin/socket-patch. This launcher script finds and
25+ // executes the platform-specific Rust binary from the optionalDependencies.
26+ function resolveSocketPatchBin ( ) : string {
27+ // Walk up from this file (or dist/) to find the closest node_modules/.bin.
28+ let dir = __dirname
29+ for ( let i = 0 ; i < 10 ; i += 1 ) {
30+ const candidate = path . join ( dir , 'node_modules' , '.bin' , 'socket-patch' )
31+ if ( existsSync ( candidate ) ) {
32+ return candidate
33+ }
34+ const parent = path . dirname ( dir )
35+ if ( parent === dir ) {
36+ break
37+ }
38+ dir = parent
39+ }
40+ // Fallback: assume socket-patch is on PATH.
41+ return 'socket-patch'
42+ }
43+
1944async function run (
2045 argv : string [ ] | readonly string [ ] ,
2146 _importMeta : ImportMeta ,
2247 _context : CliCommandContext ,
2348) : Promise < void > {
2449 const { ENV } = constants
2550
26- // Map socket-cli environment to socket-patch options.
27- // Only include properties with defined values (exactOptionalPropertyTypes).
28- const options : Parameters < typeof runPatch > [ 1 ] = { }
51+ // Build environment variables for the socket-patch binary.
52+ const spawnEnv : Record < string , string | undefined > = {
53+ ...process . env ,
54+ }
2955
56+ // Map socket-cli environment to socket-patch environment variables.
3057 // Strip /v0/ suffix from API URL if present.
3158 const apiUrl = ENV . SOCKET_CLI_API_BASE_URL ?. replace ( / \/ v 0 \/ ? $ / , '' )
3259 if ( apiUrl ) {
33- options . apiUrl = apiUrl
60+ spawnEnv [ 'SOCKET_API_URL' ] = apiUrl
3461 }
3562 if ( ENV . SOCKET_CLI_API_TOKEN ) {
36- options . apiToken = ENV . SOCKET_CLI_API_TOKEN
63+ spawnEnv [ 'SOCKET_API_TOKEN' ] = ENV . SOCKET_CLI_API_TOKEN
3764 }
3865 if ( ENV . SOCKET_CLI_ORG_SLUG ) {
39- options . orgSlug = ENV . SOCKET_CLI_ORG_SLUG
66+ spawnEnv [ 'SOCKET_ORG_SLUG' ] = ENV . SOCKET_CLI_ORG_SLUG
4067 }
4168 if ( ENV . SOCKET_PATCH_PROXY_URL ) {
42- options . patchProxyUrl = ENV . SOCKET_PATCH_PROXY_URL
69+ spawnEnv [ 'SOCKET_PATCH_PROXY_URL' ] = ENV . SOCKET_PATCH_PROXY_URL
4370 }
4471 if ( ENV . SOCKET_CLI_API_PROXY ) {
45- options . httpProxy = ENV . SOCKET_CLI_API_PROXY
72+ spawnEnv [ 'HTTPS_PROXY' ] = ENV . SOCKET_CLI_API_PROXY
4673 }
4774 if ( ENV . SOCKET_CLI_DEBUG ) {
48- options . debug = ENV . SOCKET_CLI_DEBUG
75+ spawnEnv [ 'SOCKET_PATCH_DEBUG' ] = '1'
4976 }
5077
51- // Forward all arguments to socket-patch.
52- const exitCode = await runPatch ( [ ...argv ] , options )
78+ // Resolve and spawn the socket-patch Rust binary.
79+ // On Windows, node_modules/.bin shims are .cmd scripts that require shell.
80+ const binPath = resolveSocketPatchBin ( )
81+ const result = spawnSync ( binPath , [ ...argv ] , {
82+ stdio : 'inherit' ,
83+ env : spawnEnv ,
84+ shell : constants . WIN32 ,
85+ } )
5386
54- if ( exitCode !== 0 ) {
55- process . exitCode = exitCode
87+ if ( result . error ) {
88+ throw result . error
89+ }
90+ // Propagate signal if the child was killed (e.g. SIGTERM, SIGINT).
91+ if ( result . signal ) {
92+ process . kill ( process . pid , result . signal )
93+ return
94+ }
95+ if ( result . status !== null && result . status !== 0 ) {
96+ process . exitCode = result . status
5697 }
5798}
0 commit comments