diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 17df354d9..325c01474 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "@aws/agentcore", - "version": "0.27.1", + "version": "0.28.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@aws/agentcore", - "version": "0.27.1", + "version": "0.28.1", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -37,6 +37,7 @@ "@opentelemetry/sdk-metrics": "^2.6.1", "@smithy/shared-ini-file-loader": "^4.4.2", "commander": "^14.0.2", + "cross-spawn": "^7.0.6", "dotenv": "^17.2.3", "fast-json-stable-stringify": "^2.1.0", "fflate": "^0.8.2", @@ -61,6 +62,7 @@ "@playwright/test": "^1.59.1", "@secretlint/secretlint-rule-preset-recommend": "^12.2.0", "@trivago/prettier-plugin-sort-imports": "^6.0.2", + "@types/cross-spawn": "^6.0.6", "@types/js-yaml": "^4.0.9", "@types/node": "^25.0.3", "@types/react": "^19.2.7", @@ -5958,6 +5960,16 @@ "assertion-error": "^2.0.1" } }, + "node_modules/@types/cross-spawn": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/debug": { "version": "4.1.13", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", @@ -6829,22 +6841,6 @@ } } }, - "node_modules/ajv/node_modules/fast-uri": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", - "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/ansi-escapes": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", @@ -9442,6 +9438,22 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", + "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fast-xml-builder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.3.0.tgz", @@ -11086,9 +11098,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", - "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "funding": [ { "type": "github", diff --git a/package.json b/package.json index 426182343..293622248 100644 --- a/package.json +++ b/package.json @@ -104,6 +104,7 @@ "@opentelemetry/sdk-metrics": "^2.6.1", "@smithy/shared-ini-file-loader": "^4.4.2", "commander": "^14.0.2", + "cross-spawn": "^7.0.6", "dotenv": "^17.2.3", "fast-json-stable-stringify": "^2.1.0", "fflate": "^0.8.2", @@ -132,6 +133,7 @@ "@playwright/test": "^1.59.1", "@secretlint/secretlint-rule-preset-recommend": "^12.2.0", "@trivago/prettier-plugin-sort-imports": "^6.0.2", + "@types/cross-spawn": "^6.0.6", "@types/js-yaml": "^4.0.9", "@types/node": "^25.0.3", "@types/react": "^19.2.7", diff --git a/src/cli/operations/init/files.ts b/src/cli/operations/init/files.ts index 67eb9428b..25d829666 100644 --- a/src/cli/operations/init/files.ts +++ b/src/cli/operations/init/files.ts @@ -43,8 +43,7 @@ export interface InitGitRepoResult { * Skips if already in a git repo or if git is not available. */ export async function initGitRepo(projectRoot: string): Promise { - // All git commands use shell: false to avoid Windows cmd argument parsing issues - const gitOptions = { cwd: projectRoot, stdio: 'pipe' as const, shell: false }; + const gitOptions = { cwd: projectRoot, stdio: 'pipe' as const }; // Check if git is available const gitCheck = await runSubprocessCapture('git', ['--version'], gitOptions); diff --git a/src/lib/utils/subprocess.ts b/src/lib/utils/subprocess.ts index 6f19da833..1c41d3f3c 100644 --- a/src/lib/utils/subprocess.ts +++ b/src/lib/utils/subprocess.ts @@ -1,6 +1,5 @@ -import { isWindows } from './platform'; -import { spawn, spawnSync } from 'child_process'; import type { StdioOptions } from 'child_process'; +import crossSpawn from 'cross-spawn'; /** * Subprocess utilities for AgentCore. @@ -11,38 +10,21 @@ import type { StdioOptions } from 'child_process'; * Sync functions (runSubprocessCaptureSync, checkSubprocessSync) block the event loop * and are ONLY safe in CDK bundling contexts (which run in a subprocess). They are * intentionally NOT exported from the public API to prevent accidental UI freezes. + * + * Nothing spawns through a shell. cross-spawn resolves Windows .cmd/.bat wrappers and + * escapes arguments so a metacharacter in an argument (an `&` in a path, say) reaches + * the child intact instead of being interpreted by cmd.exe. */ export interface SubprocessOptions { cwd?: string; env?: NodeJS.ProcessEnv; stdio?: StdioOptions; - shell?: boolean; -} - -/** - * When shell mode is enabled, merge args into the command string so that - * Node.js does not receive both a non-empty args array and `shell: true`. - * Passing both triggers DEP0190 on Node ≥ 22 (and a warning on earlier - * versions) because the arguments are concatenated without escaping. - */ -function resolveCommand(command: string, args: string[], useShell: boolean): { cmd: string; cmdArgs: string[] } { - if (useShell) { - return { cmd: [command, ...args].join(' '), cmdArgs: [] }; - } - return { cmd: command, cmdArgs: args }; } export async function runSubprocess(command: string, args: string[], options: SubprocessOptions = {}): Promise { - const shell = options.shell ?? isWindows; - const { cmd, cmdArgs } = resolveCommand(command, args, shell); return new Promise((resolve, reject) => { - const child = spawn(cmd, cmdArgs, { - cwd: options.cwd, - env: options.env, - stdio: options.stdio ?? 'inherit', - shell, - }); + const child = crossSpawn(command, args, { ...options, stdio: options.stdio ?? 'inherit' }); child.on('error', reject); child.on('close', (code, signal) => { @@ -61,15 +43,8 @@ export async function checkSubprocess( args: string[], options: SubprocessOptions = {} ): Promise { - const shell = options.shell ?? isWindows; - const { cmd, cmdArgs } = resolveCommand(command, args, shell); return new Promise(resolve => { - const child = spawn(cmd, cmdArgs, { - cwd: options.cwd, - env: options.env, - stdio: options.stdio ?? 'ignore', - shell, - }); + const child = crossSpawn(command, args, { ...options, stdio: options.stdio ?? 'ignore' }); child.on('error', () => resolve(false)); child.on('close', code => resolve(code === 0)); @@ -88,15 +63,8 @@ export async function runSubprocessCapture( args: string[], options: SubprocessOptions = {} ): Promise { - const shell = options.shell ?? isWindows; - const { cmd, cmdArgs } = resolveCommand(command, args, shell); return new Promise(resolve => { - const child = spawn(cmd, cmdArgs, { - cwd: options.cwd, - env: options.env, - stdio: 'pipe', - shell, - }); + const child = crossSpawn(command, args, { ...options, stdio: 'pipe' }); let stdout = ''; let stderr = ''; @@ -124,15 +92,7 @@ export function runSubprocessCaptureSync( args: string[], options: SubprocessOptions = {} ): SubprocessResult { - const shell = options.shell ?? isWindows; - const { cmd, cmdArgs } = resolveCommand(command, args, shell); - const result = spawnSync(cmd, cmdArgs, { - cwd: options.cwd, - env: options.env, - stdio: 'pipe', - shell, - encoding: 'utf-8', - }); + const result = crossSpawn.sync(command, args, { ...options, stdio: 'pipe', encoding: 'utf-8' }); return { stdout: result.stdout ?? '', @@ -143,15 +103,8 @@ export function runSubprocessCaptureSync( } export function checkSubprocessSync(command: string, args: string[], options: SubprocessOptions = {}): boolean { - const shell = options.shell ?? isWindows; - const { cmd, cmdArgs } = resolveCommand(command, args, shell); try { - const result = spawnSync(cmd, cmdArgs, { - cwd: options.cwd, - env: options.env, - stdio: options.stdio ?? 'ignore', - shell, - }); + const result = crossSpawn.sync(command, args, { ...options, stdio: options.stdio ?? 'ignore' }); return result.status === 0; } catch { return false;