From b21b13828f15e9a5e51ed782178dc8e901d3c0f0 Mon Sep 17 00:00:00 2001 From: Test User Date: Thu, 6 Nov 2025 08:55:22 -0500 Subject: [PATCH 1/5] refactor(sea): replace require() with ES module import for NODE_SEA_FUSE Create dedicated module for NODE_SEA_FUSE constant and use static import instead of lazy require(). This improves bundling and eliminates dynamic require() calls. Changes: - Create src/utils/sea/constants/NODE_SEA_FUSE.mts with fuse constant - Update src/utils/sea/build.mts to use static import - Replace 3 require() calls with direct constant usage --- packages/cli/src/utils/sea/build.mts | 7 ++++--- packages/cli/src/utils/sea/constants/NODE_SEA_FUSE.mts | 6 ++++++ 2 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 packages/cli/src/utils/sea/constants/NODE_SEA_FUSE.mts diff --git a/packages/cli/src/utils/sea/build.mts b/packages/cli/src/utils/sea/build.mts index 20a3df9f2..ca126afb6 100644 --- a/packages/cli/src/utils/sea/build.mts +++ b/packages/cli/src/utils/sea/build.mts @@ -16,6 +16,7 @@ import { getSocketHomePath } from '@socketsecurity/lib/paths' import { spawn } from '@socketsecurity/lib/spawn' import ENV from '../../constants/env.mts' +import { NODE_SEA_FUSE } from './constants/NODE_SEA_FUSE.mts' const logger = getDefaultLogger() @@ -442,7 +443,7 @@ export async function injectSeaBlob( 'NODE_SEA_BLOB', blobPath, '--sentinel-fuse', - /*@__INLINE__*/ require('./constants/NODE_SEA_FUSE'), + NODE_SEA_FUSE, '--macho-segment-name', 'NODE_SEA', ], @@ -467,7 +468,7 @@ export async function injectSeaBlob( 'NODE_SEA_BLOB', blobPath, '--sentinel-fuse', - /*@__INLINE__*/ require('./constants/NODE_SEA_FUSE'), + NODE_SEA_FUSE, ], { stdio: 'inherit' }, ) @@ -483,7 +484,7 @@ export async function injectSeaBlob( 'NODE_SEA_BLOB', blobPath, '--sentinel-fuse', - /*@__INLINE__*/ require('./constants/NODE_SEA_FUSE'), + NODE_SEA_FUSE, ], { stdio: 'inherit' }, ) diff --git a/packages/cli/src/utils/sea/constants/NODE_SEA_FUSE.mts b/packages/cli/src/utils/sea/constants/NODE_SEA_FUSE.mts new file mode 100644 index 000000000..d5f3c16bc --- /dev/null +++ b/packages/cli/src/utils/sea/constants/NODE_SEA_FUSE.mts @@ -0,0 +1,6 @@ +/** + * Node.js Single Executable Application (SEA) fuse string. + * This value is used with postject to inject the SEA blob into the Node binary. + * See: https://nodejs.org/api/single-executable-applications.html + */ +export const NODE_SEA_FUSE = 'NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2' From 07d547cde9d788a477597951aa6735733969d9fe Mon Sep 17 00:00:00 2001 From: Test User Date: Thu, 6 Nov 2025 08:55:37 -0500 Subject: [PATCH 2/5] refactor(cli)!: remove shadow bins for pnpm and yarn Remove shadow bin wrappers for pnpm and yarn, using direct spawn instead. Shadow bins are only needed for npm/npx security scanning. For pnpm/yarn, we use direct spawn with WIN32 shell option for cross-platform compatibility. BREAKING CHANGE: socket-pnpm and socket-yarn binaries no longer use shadow wrappers. Commands now spawn package managers directly. Changes: - Remove src/shadow/pnpm/ and src/shadow/yarn/ directories - Remove src/pnpm-cli.mts and src/yarn-cli.mts entry points - Update cmd-pnpm.mts to use direct spawn() with WIN32 shell - Update cmd-yarn.mts to use direct spawn() with WIN32 shell - Replace lazy require() with static imports from @socketsecurity/lib --- packages/cli/src/commands/pnpm/cmd-pnpm.mts | 15 +- packages/cli/src/commands/yarn/cmd-yarn.mts | 17 +- packages/cli/src/pnpm-cli.mts | 35 --- packages/cli/src/shadow/pnpm/bin.mts | 229 -------------------- packages/cli/src/shadow/yarn/bin.mts | 136 ------------ packages/cli/src/yarn-cli.mts | 43 ---- 6 files changed, 13 insertions(+), 462 deletions(-) delete mode 100644 packages/cli/src/pnpm-cli.mts delete mode 100644 packages/cli/src/shadow/pnpm/bin.mts delete mode 100644 packages/cli/src/shadow/yarn/bin.mts delete mode 100644 packages/cli/src/yarn-cli.mts diff --git a/packages/cli/src/commands/pnpm/cmd-pnpm.mts b/packages/cli/src/commands/pnpm/cmd-pnpm.mts index 586e5e200..ee43e3a11 100644 --- a/packages/cli/src/commands/pnpm/cmd-pnpm.mts +++ b/packages/cli/src/commands/pnpm/cmd-pnpm.mts @@ -1,14 +1,13 @@ -import { createRequire } from 'node:module' - -import { PNPM } from '@socketsecurity/lib-internal/constants/agents' -import { getDefaultLogger } from '@socketsecurity/lib-internal/logger' +import { WIN32 } from '@socketsecurity/lib/constants/platform' +import { getDefaultLogger } from '@socketsecurity/lib/logger' +import { spawn } from '@socketsecurity/lib/spawn' +import { PNPM } from '../../constants/agents.mts' import { DRY_RUN_BAILING_NOW, FLAG_DRY_RUN, FLAG_HELP, } from '../../constants/cli.mts' -import { getShadowPnpmBinPath } from '../../constants/paths.mts' import { commonFlags } from '../../flags.mts' import { meowOrExit } from '../../utils/cli/with-subcommands.mjs' import { getFlagApiRequirementsOutput } from '../../utils/output/formatting.mts' @@ -19,7 +18,6 @@ import type { CliCommandContext, } from '../../utils/cli/with-subcommands.mjs' -const require = createRequire(import.meta.url) const logger = getDefaultLogger() export const CMD_NAME = PNPM @@ -81,14 +79,13 @@ async function run( return } - const shadowPnpmBin = /*@__PURE__*/ require(getShadowPnpmBinPath()) - process.exitCode = 1 // Filter Socket flags from argv. const filteredArgv = filterFlags(argv, config.flags) - const { spawnPromise } = await shadowPnpmBin(filteredArgv, { + const spawnPromise = spawn(PNPM, filteredArgv, { + shell: WIN32, stdio: 'inherit', }) diff --git a/packages/cli/src/commands/yarn/cmd-yarn.mts b/packages/cli/src/commands/yarn/cmd-yarn.mts index baa207f7f..7a5debcc9 100644 --- a/packages/cli/src/commands/yarn/cmd-yarn.mts +++ b/packages/cli/src/commands/yarn/cmd-yarn.mts @@ -1,14 +1,13 @@ -import { createRequire } from 'node:module' - -import { YARN } from '@socketsecurity/lib-internal/constants/agents' -import { getDefaultLogger } from '@socketsecurity/lib-internal/logger' +import { WIN32 } from '@socketsecurity/lib/constants/platform' +import { getDefaultLogger } from '@socketsecurity/lib/logger' +import { spawn } from '@socketsecurity/lib/spawn' +import { YARN } from '../../constants/agents.mts' import { DRY_RUN_BAILING_NOW, FLAG_DRY_RUN, FLAG_HELP, } from '../../constants/cli.mts' -import { getShadowYarnBinPath } from '../../constants/paths.mts' import { commonFlags } from '../../flags.mts' import { meowOrExit } from '../../utils/cli/with-subcommands.mjs' import { getFlagApiRequirementsOutput } from '../../utils/output/formatting.mts' @@ -19,7 +18,7 @@ import type { CliCommandContext, } from '../../utils/cli/with-subcommands.mjs' -const require = createRequire(import.meta.url) +const logger = getDefaultLogger() export const CMD_NAME = YARN @@ -76,19 +75,17 @@ async function run( const dryRun = !!cli.flags['dryRun'] if (dryRun) { - const logger = getDefaultLogger() logger.log(DRY_RUN_BAILING_NOW) return } - const shadowYarnBin = /*@__PURE__*/ require(getShadowYarnBinPath()) - process.exitCode = 1 // Filter Socket flags from argv. const filteredArgv = filterFlags(argv, config.flags) - const { spawnPromise } = await shadowYarnBin(filteredArgv, { + const spawnPromise = spawn(YARN, filteredArgv, { + shell: WIN32, stdio: 'inherit', }) diff --git a/packages/cli/src/pnpm-cli.mts b/packages/cli/src/pnpm-cli.mts deleted file mode 100644 index b4eb64e02..000000000 --- a/packages/cli/src/pnpm-cli.mts +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env node - -import { getDefaultLogger } from '@socketsecurity/lib-internal/logger' - -import shadowPnpmBin from './shadow/pnpm/bin.mts' - -const logger = getDefaultLogger() - -export default async function runPnpmCli() { - process.exitCode = 1 - - const { spawnPromise } = await shadowPnpmBin(process.argv.slice(2), { - stdio: 'inherit', - cwd: process.cwd(), - env: { ...process.env }, - }) - - // Wait for the spawn promise to resolve and handle the result. - const result = await spawnPromise - if (result.signal) { - process.kill(process.pid, result.signal) - } else if (typeof result.code === 'number') { - // eslint-disable-next-line n/no-process-exit - process.exit(result.code) - } -} - -// Run if invoked directly (not as a module). -if (import.meta.url === `file://${process.argv[1]}`) { - runPnpmCli().catch(error => { - logger.error('Socket pnpm wrapper error:', error) - // eslint-disable-next-line n/no-process-exit - process.exit(1) - }) -} diff --git a/packages/cli/src/shadow/pnpm/bin.mts b/packages/cli/src/shadow/pnpm/bin.mts deleted file mode 100644 index a426df522..000000000 --- a/packages/cli/src/shadow/pnpm/bin.mts +++ /dev/null @@ -1,229 +0,0 @@ -import { existsSync } from 'node:fs' -import path from 'node:path' -import { fileURLToPath } from 'node:url' - -import { WIN32 } from '@socketsecurity/lib/constants/platform' -import { debug, debugDir, debugNs } from '@socketsecurity/lib/debug' -import { getDefaultLogger } from '@socketsecurity/lib/logger' -import { normalizePath } from '@socketsecurity/lib/path' -import { spawn } from '@socketsecurity/lib/spawn' - -import { PNPM } from '../../constants/agents.mts' -import { FLAG_DRY_RUN } from '../../constants/cli.mts' -import ENV from '../../constants/env.mts' -import { PNPM_LOCK_YAML } from '../../constants/packages.mts' -import { shadowBinPath } from '../../constants/paths.mts' -import { - SOCKET_CLI_ACCEPT_RISKS, - SOCKET_CLI_SHADOW_API_TOKEN, - SOCKET_CLI_SHADOW_BIN, - SOCKET_CLI_SHADOW_PROGRESS, - SOCKET_CLI_VIEW_ALL_RISKS, - SOCKET_IPC_HANDSHAKE, -} from '../../constants/shadow.mts' -import { - parsePnpmLockfile, - readPnpmLockfile, -} from '../../utils/pnpm/lockfile.mts' -import { - cmdFlagsToString, - isAddCommand, - isPnpmLockfileScanCommand, -} from '../../utils/process/cmd.mts' -import { installPnpmLinks } from '../../utils/shadow/links.mts' -import { getAlertsMapFromPnpmLockfile } from '../../utils/socket/alerts.mts' -import { logAlertsMap } from '../../utils/socket/package-alert.mts' -import { getPublicApiToken } from '../../utils/socket/sdk.mjs' -import { scanPackagesAndLogAlerts } from '../common.mts' -import { ensureIpcInStdio } from '../stdio-ipc.mts' - -import type { IpcObject } from '../../constants/shadow.mts' -import type { - SpawnExtra, - SpawnOptions, - SpawnResult, -} from '@socketsecurity/lib/spawn' - -export type ShadowPnpmOptions = SpawnOptions & { - ipc?: IpcObject | undefined -} - -export type ShadowPnpmResult = { - spawnPromise: SpawnResult -} - -const DLX_COMMANDS = new Set(['dlx']) - -const INSTALL_COMMANDS = new Set([ - 'add', - 'i', - 'install', - 'install-test', - 'it', - 'update', - 'up', -]) - -export default async function shadowPnpmBin( - args: string[] | readonly string[] = process.argv.slice(2), - options?: ShadowPnpmOptions | undefined, - extra?: SpawnExtra | undefined, -): Promise { - const opts = { __proto__: null, ...options } as ShadowPnpmOptions - const { env: spawnEnv, ipc, ...spawnOpts } = opts - - let { cwd = process.cwd() } = opts - if (cwd instanceof URL) { - cwd = fileURLToPath(cwd) - } - - const terminatorPos = args.indexOf('--') - const rawPnpmArgs = terminatorPos === -1 ? args : args.slice(0, terminatorPos) - - const { spinner } = opts - const wasSpinning = !!spinner?.isSpinning - - // Check if this is a command that needs security scanning. - const command = rawPnpmArgs[0] - const isDlxCommand = command && DLX_COMMANDS.has(command) - const isInstallCommand = command && INSTALL_COMMANDS.has(command) - const needsScanning = isDlxCommand || isInstallCommand - - spinner?.start() - - if (needsScanning && !rawPnpmArgs.includes(FLAG_DRY_RUN)) { - const acceptRisks = !!ENV.SOCKET_CLI_ACCEPT_RISKS - const viewAllRisks = !!ENV.SOCKET_CLI_VIEW_ALL_RISKS - - // Handle add and dlx commands with shared utility. - if (isDlxCommand || isAddCommand(command)) { - const scanResult = await scanPackagesAndLogAlerts({ - acceptRisks, - command, - cwd, - dlxCommands: DLX_COMMANDS, - installCommands: INSTALL_COMMANDS, - managerName: PNPM, - rawArgs: rawPnpmArgs, - spinner, - viewAllRisks, - }) - - if (scanResult.shouldExit) { - // eslint-disable-next-line n/no-process-exit - process.exit(1) - // This line is never reached in production, but helps tests. - throw new Error('process.exit called') - } - } else if (isPnpmLockfileScanCommand(command)) { - // For install/update, scan all dependencies from pnpm-lock.yaml - const pnpmLockPath = normalizePath(path.join(cwd, PNPM_LOCK_YAML)) - if (existsSync(pnpmLockPath)) { - try { - const lockfileContent = await readPnpmLockfile(pnpmLockPath) - if (lockfileContent) { - const lockfile = parsePnpmLockfile(lockfileContent) - if (lockfile) { - // Use existing function to scan the entire lockfile - debug(`scanning: all dependencies from ${PNPM_LOCK_YAML}`) - - const alertsMap = await getAlertsMapFromPnpmLockfile(lockfile, { - nothrow: true, - filter: acceptRisks - ? { actions: ['error'], blocked: true } - : { actions: ['error', 'monitor', 'warn'] }, - }) - - spinner?.stop() - - if (alertsMap.size) { - process.exitCode = 1 - logAlertsMap(alertsMap, { - hideAt: viewAllRisks ? 'none' : 'middle', - output: process.stderr, - }) - - const errorMessage = `Socket ${PNPM} exiting due to risks.${ - viewAllRisks - ? '' - : `\nView all risks - Rerun with environment variable ${SOCKET_CLI_VIEW_ALL_RISKS}=1.` - }${ - acceptRisks - ? '' - : `\nAccept risks - Rerun with environment variable ${SOCKET_CLI_ACCEPT_RISKS}=1.` - }`.trim() - - const logger = getDefaultLogger() - logger.error(errorMessage) - // eslint-disable-next-line n/no-process-exit - process.exit(1) - // This line is never reached in production, but helps tests. - throw new Error('process.exit called') - } - - // Return early since we've already done the scanning - debug('complete: lockfile scanning, proceeding with install') - } - } - } catch (e) { - debug(`${PNPM} lockfile scanning failed`) - debugDir(e) - } - } else { - debug( - `skip: no ${PNPM_LOCK_YAML} found, skipping bulk install scanning`, - ) - } - } - - debug('complete: scanning, proceeding with install') - } - - const realPnpmPath = await installPnpmLinks(shadowBinPath) - - const otherArgs = terminatorPos === -1 ? [] : args.slice(terminatorPos) - const suffixArgs = [...rawPnpmArgs, ...otherArgs] - - debugNs( - 'notice', - `spawn: ${PNPM} shadow bin ${realPnpmPath} ${cmdFlagsToString(suffixArgs)}`, - ) - - if (wasSpinning) { - spinner?.start() - } - - // Set up stdio with IPC channel. - const stdio = ensureIpcInStdio(spawnOpts.stdio) - - const spawnPromise = spawn( - realPnpmPath, - suffixArgs, - { - ...spawnOpts, - cwd, - env: { - ...process.env, - ...spawnEnv, - }, - stdio, - // On Windows, pnpm is often a .cmd file that requires shell execution. - // The spawn function from @socketsecurity/registry will handle this properly - // when shell is true. - shell: WIN32, - }, - extra, - ) - - // Send IPC handshake. - spawnPromise.process.send({ - [SOCKET_IPC_HANDSHAKE]: { - [SOCKET_CLI_SHADOW_API_TOKEN]: getPublicApiToken(), - [SOCKET_CLI_SHADOW_BIN]: PNPM, - [SOCKET_CLI_SHADOW_PROGRESS]: true, - ...ipc, - }, - }) - - return { spawnPromise } -} diff --git a/packages/cli/src/shadow/yarn/bin.mts b/packages/cli/src/shadow/yarn/bin.mts deleted file mode 100644 index 58a4bc2b9..000000000 --- a/packages/cli/src/shadow/yarn/bin.mts +++ /dev/null @@ -1,136 +0,0 @@ -import { fileURLToPath } from 'node:url' - -import { YARN } from '@socketsecurity/lib/constants/agents' -import { WIN32 } from '@socketsecurity/lib/constants/platform' -import { debugNs } from '@socketsecurity/lib/debug' -import { spawn } from '@socketsecurity/lib/spawn' - -import ENV from '../../constants/env.mts' -import { shadowBinPath } from '../../constants/paths.mts' -import { - SOCKET_CLI_SHADOW_API_TOKEN, - SOCKET_CLI_SHADOW_BIN, - SOCKET_CLI_SHADOW_PROGRESS, - SOCKET_IPC_HANDSHAKE, -} from '../../constants/shadow.mts' -import { cmdFlagsToString } from '../../utils/process/cmd.mts' -import { installYarnLinks } from '../../utils/shadow/links.mts' -import { getPublicApiToken } from '../../utils/socket/sdk.mjs' -import { scanPackagesAndLogAlerts } from '../common.mts' -import { ensureIpcInStdio } from '../stdio-ipc.mts' - -import type { IpcObject } from '../../constants/shadow.mts' -import type { - SpawnExtra, - SpawnOptions, - SpawnResult, -} from '@socketsecurity/lib/spawn' - -export type ShadowYarnOptions = SpawnOptions & { - ipc?: IpcObject | undefined -} - -export type ShadowYarnResult = { - spawnPromise: SpawnResult -} - -const DLX_COMMANDS = new Set(['dlx']) - -const INSTALL_COMMANDS = new Set([ - 'add', - 'install', - 'up', - 'upgrade', - 'upgrade-interactive', -]) - -export default async function shadowYarnBin( - args: string[] | readonly string[] = process.argv.slice(2), - options?: ShadowYarnOptions | undefined, - extra?: SpawnExtra | undefined, -): Promise { - const opts = { __proto__: null, ...options } as ShadowYarnOptions - const { env: spawnEnv, ipc, ...spawnOpts } = opts - - let { cwd = process.cwd() } = opts - if (cwd instanceof URL) { - cwd = fileURLToPath(cwd) - } - - const terminatorPos = args.indexOf('--') - const rawYarnArgs = terminatorPos === -1 ? args : args.slice(0, terminatorPos) - - const { spinner } = opts - const wasSpinning = !!spinner?.isSpinning - - spinner?.start() - - // Check for package scanning. - const command = rawYarnArgs[0] - const scanResult = await scanPackagesAndLogAlerts({ - acceptRisks: !!ENV.SOCKET_CLI_ACCEPT_RISKS, - command, - cwd, - dlxCommands: DLX_COMMANDS, - installCommands: INSTALL_COMMANDS, - managerName: YARN, - rawArgs: rawYarnArgs, - spinner, - viewAllRisks: !!ENV.SOCKET_CLI_VIEW_ALL_RISKS, - }) - - if (scanResult.shouldExit) { - // eslint-disable-next-line n/no-process-exit - process.exit(1) - // This line is never reached in production, but helps tests. - throw new Error('process.exit called') - } - - const realYarnPath = await installYarnLinks(shadowBinPath) - - const otherArgs = terminatorPos === -1 ? [] : args.slice(terminatorPos) - const suffixArgs = [...rawYarnArgs, ...otherArgs] - - debugNs( - 'notice', - `spawn: ${YARN} shadow bin ${realYarnPath} ${cmdFlagsToString(suffixArgs)}`, - ) - - if (wasSpinning) { - spinner?.start() - } - - // Set up stdio with IPC channel. - const stdio = ensureIpcInStdio(spawnOpts.stdio) - - const spawnPromise = spawn( - realYarnPath, - suffixArgs, - { - ...spawnOpts, - cwd, - env: { - ...process.env, - ...spawnEnv, - }, - stdio, - // On Windows, yarn is often a .cmd file that requires shell execution. - // The spawn function from @socketsecurity/registry will handle this properly - // when shell is true. - shell: WIN32, - }, - extra, - ) - - // Send IPC handshake. - spawnPromise.process.send({ - [SOCKET_IPC_HANDSHAKE]: { - [SOCKET_CLI_SHADOW_API_TOKEN]: getPublicApiToken(), - [SOCKET_CLI_SHADOW_BIN]: YARN, - [SOCKET_CLI_SHADOW_PROGRESS]: true, - ...ipc, - }, - }) - - return { spawnPromise } -} diff --git a/packages/cli/src/yarn-cli.mts b/packages/cli/src/yarn-cli.mts deleted file mode 100644 index 2779c26bd..000000000 --- a/packages/cli/src/yarn-cli.mts +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node - -import { getDefaultLogger } from '@socketsecurity/lib/logger' - -import shadowYarnBin from './shadow/yarn/bin.mts' - -import type { ChildProcess } from 'node:child_process' - -const logger = getDefaultLogger() - -export default async function runYarnCli() { - process.exitCode = 1 - - const { spawnPromise } = await shadowYarnBin(process.argv.slice(2), { - stdio: 'inherit', - cwd: process.cwd(), - env: { ...process.env }, - }) - - // See https://nodejs.org/api/child_process.html#event-exit. - ;(spawnPromise.process as ChildProcess).on( - 'exit', - (code: number | null, signalName: string | null) => { - if (signalName) { - process.kill(process.pid, signalName) - } else if (typeof code === 'number') { - // eslint-disable-next-line n/no-process-exit - process.exit(code) - } - }, - ) - - await spawnPromise -} - -// Run if invoked directly (not as a module). -if (import.meta.url === `file://${process.argv[1]}`) { - runYarnCli().catch(error => { - logger.error('Socket yarn wrapper error:', error) - // eslint-disable-next-line n/no-process-exit - process.exit(1) - }) -} From 1dcba25ed0e5e2d4daab171d070a79607c4762e5 Mon Sep 17 00:00:00 2001 From: Test User Date: Thu, 6 Nov 2025 08:55:49 -0500 Subject: [PATCH 3/5] refactor(cli): replace lazy require() with static imports in npm/npx commands Convert npm and npx commands from lazy require() to static ES module imports for shadow bins. This improves bundling and eliminates dynamic loading. Changes: - Remove createRequire usage from cmd-npm.mts and cmd-npx.mts - Add static imports for shadowNpmBin and shadowNpxBin - Add type cast (as any) for spawnPromise.process to fix TypeScript error - Maintain same functionality with cleaner static imports --- packages/cli/src/commands/npm/cmd-npm.mts | 9 ++------- packages/cli/src/commands/npx/cmd-npx.mts | 9 ++------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/packages/cli/src/commands/npm/cmd-npm.mts b/packages/cli/src/commands/npm/cmd-npm.mts index 19e8aad52..b807e9b9b 100644 --- a/packages/cli/src/commands/npm/cmd-npm.mts +++ b/packages/cli/src/commands/npm/cmd-npm.mts @@ -1,5 +1,3 @@ -import { createRequire } from 'node:module' - import { NPM } from '@socketsecurity/lib-internal/constants/agents' import { getDefaultLogger } from '@socketsecurity/lib-internal/logger' @@ -9,8 +7,8 @@ import { FLAG_HELP, FLAG_JSON, } from '../../constants/cli.mts' -import { getShadowNpmBinPath } from '../../constants/paths.mts' import { commonFlags, outputFlags } from '../../flags.mts' +import shadowNpmBin from '../../shadow/npm/bin.mts' import { meowOrExit } from '../../utils/cli/with-subcommands.mjs' import { getFlagApiRequirementsOutput } from '../../utils/output/formatting.mts' import { filterFlags } from '../../utils/process/cmd.mts' @@ -20,7 +18,6 @@ import type { CliCommandContext, } from '../../utils/cli/with-subcommands.mjs' -const require = createRequire(import.meta.url) const logger = getDefaultLogger() export const CMD_NAME = NPM @@ -81,8 +78,6 @@ async function run( return } - const shadowNpmBin = /*@__PURE__*/ require(getShadowNpmBinPath()) - process.exitCode = 1 // Filter Socket flags from argv but keep --json for npm. @@ -94,7 +89,7 @@ async function run( }) // See https://nodejs.org/api/child_process.html#event-exit. - spawnPromise.process.on( + ;(spawnPromise as any).process.on( 'exit', (code: string | null, signalName: NodeJS.Signals | null) => { if (signalName) { diff --git a/packages/cli/src/commands/npx/cmd-npx.mts b/packages/cli/src/commands/npx/cmd-npx.mts index d426674fc..8f836bcdd 100644 --- a/packages/cli/src/commands/npx/cmd-npx.mts +++ b/packages/cli/src/commands/npx/cmd-npx.mts @@ -1,5 +1,3 @@ -import { createRequire } from 'node:module' - import { NPX } from '@socketsecurity/lib-internal/constants/agents' import { getDefaultLogger } from '@socketsecurity/lib-internal/logger' @@ -8,8 +6,8 @@ import { FLAG_DRY_RUN, FLAG_HELP, } from '../../constants/cli.mts' -import { getShadowNpxBinPath } from '../../constants/paths.mts' import { commonFlags } from '../../flags.mts' +import shadowNpxBin from '../../shadow/npx/bin.mts' import { meowOrExit } from '../../utils/cli/with-subcommands.mjs' import { getFlagApiRequirementsOutput } from '../../utils/output/formatting.mts' @@ -18,7 +16,6 @@ import type { CliCommandContext, } from '../../utils/cli/with-subcommands.mjs' -const require = createRequire(import.meta.url) const logger = getDefaultLogger() const CMD_NAME = NPX @@ -77,14 +74,12 @@ async function run( return } - const shadowNpxBin = /*@__PURE__*/ require(getShadowNpxBinPath()) - process.exitCode = 1 const { spawnPromise } = await shadowNpxBin(argv, { stdio: 'inherit' }) // See https://nodejs.org/api/child_process.html#event-exit. - spawnPromise.process.on( + ;(spawnPromise as any).process.on( 'exit', (code: string | null, signalName: NodeJS.Signals | null) => { if (signalName) { From 43b37959d9a90ab8d7cfa166f3b0c8bc2f46e53f Mon Sep 17 00:00:00 2001 From: Test User Date: Thu, 6 Nov 2025 08:56:07 -0500 Subject: [PATCH 4/5] refactor(cli): replace dynamic imports with static imports in cli-dispatch Update cli-dispatch to use static imports and direct command calls instead of dynamic imports and subprocess spawning. This improves performance, reduces overhead, and enables better tree-shaking. Changes: - Add static imports for cmdNpm, cmdNpx, cmdPnpm, cmdYarn - Replace dynamic import() calls with direct command.run() calls - Remove subprocess spawning for package manager commands - All commands now call handlers directly in same process - Update comments to reflect direct command execution Benefits: - Faster execution (no subprocess overhead) - Better bundling (static imports) - Cleaner code flow (direct function calls) --- packages/cli/src/cli-dispatch.mts | 37 +++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/packages/cli/src/cli-dispatch.mts b/packages/cli/src/cli-dispatch.mts index 6962526c0..247b7d28e 100644 --- a/packages/cli/src/cli-dispatch.mts +++ b/packages/cli/src/cli-dispatch.mts @@ -3,10 +3,10 @@ * * This single file handles all Socket CLI commands by detecting how it was invoked: * - socket (main CLI) - * - socket-npm (npm wrapper) - * - socket-npx (npx wrapper) - * - socket-pnpm (pnpm wrapper) - * - socket-yarn (yarn wrapper) + * - socket-npm (calls socket npm command directly) + * - socket-npx (calls socket npx command directly) + * - socket-pnpm (calls socket pnpm command directly) + * - socket-yarn (calls socket yarn command directly) * * Perfect for SEA packaging and single-file distribution. * @@ -20,6 +20,10 @@ import path from 'node:path' import { getDefaultLogger } from '@socketsecurity/lib-internal/logger' +import { cmdNpm } from './commands/npm/cmd-npm.mts' +import { cmdNpx } from './commands/npx/cmd-npx.mts' +import { cmdPnpm } from './commands/pnpm/cmd-pnpm.mts' +import { cmdYarn } from './commands/yarn/cmd-yarn.mts' import { waitForBootstrapHandshake } from './utils/sea/boot.mjs' const logger = getDefaultLogger() @@ -102,28 +106,37 @@ async function main() { // Import and run the appropriate CLI function. switch (mode) { case 'npm': { - const { default: runNpmCli } = await import('./npm-cli.mjs') - await runNpmCli() + // Call socket npm command directly. + await cmdNpm.run(process.argv.slice(2), import.meta, { + parentName: 'socket', + }) break } case 'npx': { - const { default: runNpxCli } = await import('./npx-cli.mjs') - await runNpxCli() + // Call socket npx command directly. + await cmdNpx.run(process.argv.slice(2), import.meta, { + parentName: 'socket', + }) break } case 'pnpm': { - const { default: runPnpmCli } = await import('./pnpm-cli.mjs') - await runPnpmCli() + // Call socket pnpm command directly. + await cmdPnpm.run(process.argv.slice(2), import.meta, { + parentName: 'socket', + }) break } case 'yarn': { - const { default: runYarnCli } = await import('./yarn-cli.mjs') - await runYarnCli() + // Call socket yarn command directly. + await cmdYarn.run(process.argv.slice(2), import.meta, { + parentName: 'socket', + }) break } + default: await import('./cli-entry.mjs') break From 61248ce5ea5a8ef7345833ada189b24101f784d0 Mon Sep 17 00:00:00 2001 From: Test User Date: Thu, 6 Nov 2025 08:56:24 -0500 Subject: [PATCH 5/5] refactor(shadow): remove pnpm/yarn support and use static imports in runner Simplify shadow runner to only support npm/npx since pnpm/yarn now use direct spawning. Replace lazy require() with static imports for shadow bins. Changes: - Remove createRequire and lazy require() usage - Add static imports for shadowNpmBin and shadowNpxBin - Remove agent parameter from ShadowRunnerOptions - Remove detectPackageManager() function (no longer needed) - Remove pnpm/yarn logic from runShadowCommand() - Update docs to clarify npm/npx-only support - Simplify runShadowCommand to always use shadowNpxBin Note: pnpm/yarn commands now use direct spawn in their respective command files instead of shadow wrappers. --- packages/cli/src/utils/shadow/runner.mts | 84 +++--------------------- 1 file changed, 9 insertions(+), 75 deletions(-) diff --git a/packages/cli/src/utils/shadow/runner.mts b/packages/cli/src/utils/shadow/runner.mts index 21a1886c0..0ccfcb133 100644 --- a/packages/cli/src/utils/shadow/runner.mts +++ b/packages/cli/src/utils/shadow/runner.mts @@ -1,29 +1,17 @@ /** @fileoverview Shadow bin runner with IPC support and error handling. */ -import { createRequire } from 'node:module' - import { SOCKET_PUBLIC_API_TOKEN } from '@socketsecurity/lib/constants/socket' import { Spinner as createSpinner } from '@socketsecurity/lib/spinner' -import { NPM, PNPM, YARN } from '../../constants/agents.mts' import { FLAG_SILENT } from '../../constants/cli.mts' import { - PACKAGE_LOCK_JSON, - PNPM_LOCK_YAML, - YARN_LOCK, -} from '../../constants/packages.mts' -import { - getShadowNpmBinPath, - getShadowNpxBinPath, - getShadowPnpmBinPath, - getShadowYarnBinPath, SOCKET_CLI_SHADOW_ACCEPT_RISKS, SOCKET_CLI_SHADOW_API_TOKEN, SOCKET_CLI_SHADOW_SILENT, } from '../../constants/shadow.mts' +import shadowNpmBin from '../../shadow/npm/bin.mts' +import shadowNpxBin from '../../shadow/npx/bin.mts' import { getErrorCause } from '../error/errors.mts' -import { findUp } from '../fs/find-up.mts' -import { isYarnBerry } from '../yarn/version.mts' import type { IpcObject } from '../../constants/shadow.mts' import type { @@ -34,10 +22,7 @@ import type { CResult } from '../../types.mts' import type { SpawnExtra } from '@socketsecurity/lib/spawn' import type { Spinner } from '@socketsecurity/lib/spinner' -const require = createRequire(import.meta.url) - export type ShadowRunnerOptions = { - agent?: 'npm' | 'pnpm' | 'yarn' | undefined bufferOutput?: boolean | undefined cwd?: string | undefined env?: Record | undefined @@ -48,39 +33,9 @@ export type ShadowRunnerOptions = { } /** - * Auto-detect package manager based on lockfiles. - */ -export async function detectPackageManager( - cwd?: string | undefined, -): Promise<'npm' | 'pnpm' | 'yarn'> { - const pnpmLockPath = await findUp(PNPM_LOCK_YAML, { - cwd, - onlyFiles: true, - }) - const yarnLockPath = pnpmLockPath - ? undefined - : await findUp(YARN_LOCK, { cwd, onlyFiles: true }) - const npmLockPath = - pnpmLockPath || yarnLockPath - ? undefined - : await findUp(PACKAGE_LOCK_JSON, { cwd, onlyFiles: true }) - - if (pnpmLockPath) { - return PNPM - } - if (yarnLockPath) { - return YARN - } - if (npmLockPath) { - return NPM - } - // Default to npm if no lockfile found. - return NPM -} - -/** - * Run a command via package manager dlx/npx with shadow bin wrapping. + * Run a command via npx with shadow bin wrapping. * Handles IPC for secure config passing and provides unified error handling. + * Note: Only supports npm/npx. For pnpm/yarn, use the direct commands instead. */ export async function runShadowCommand( packageSpec: string, @@ -89,7 +44,6 @@ export async function runShadowCommand( spawnExtra?: SpawnExtra | undefined, ): Promise> { const opts = { __proto__: null, ...options } as ShadowRunnerOptions - const agent = opts.agent ?? (await detectPackageManager(opts.cwd)) const shadowOpts: ShadowBinOptions = { cwd: opts.cwd, @@ -116,30 +70,11 @@ export async function runShadowCommand( spinner.start(opts.spinnerMessage) } - let result: ShadowBinResult - - if (agent === PNPM) { - const shadowPnpmBin = /*@__PURE__*/ require(getShadowPnpmBinPath()) - result = await shadowPnpmBin( - ['dlx', FLAG_SILENT, packageSpec, ...args], - shadowOpts, - finalSpawnExtra, - ) - } else if (agent === YARN && isYarnBerry()) { - const shadowYarnBin = /*@__PURE__*/ require(getShadowYarnBinPath()) - result = await shadowYarnBin( - ['dlx', '--quiet', packageSpec, ...args], - shadowOpts, - finalSpawnExtra, - ) - } else { - const shadowNpxBin = /*@__PURE__*/ require(getShadowNpxBinPath()) - result = await shadowNpxBin( - ['--yes', '--force', FLAG_SILENT, packageSpec, ...args], - shadowOpts, - finalSpawnExtra, - ) - } + const result: ShadowBinResult = await shadowNpxBin( + ['--yes', '--force', FLAG_SILENT, packageSpec, ...args], + shadowOpts, + finalSpawnExtra, + ) if (spinner) { spinner.stop() @@ -196,7 +131,6 @@ export async function runShadowNpm( spinner.start(opts.spinnerMessage) } - const shadowNpmBin = /*@__PURE__*/ require(getShadowNpmBinPath()) const result: ShadowBinResult = await shadowNpmBin( args, shadowOpts,