diff --git a/README.md b/README.md index ee68cb0b..97f25dfa 100644 --- a/README.md +++ b/README.md @@ -17,17 +17,21 @@ AgentCore with minimal configuration. ## Installation -```bash -npm install -g @aws/agentcore -``` - -> **Public Preview**: If you previously used the -> [Bedrock AgentCore Starter Toolkit](https://github.com/aws/bedrock-agentcore-starter-toolkit), uninstall it before -> using this CLI: +> **Upgrading from the Bedrock AgentCore Starter Toolkit?** The old Python CLI conflicts with this package. If it is +> still installed, `npm install` will fail with an error telling you which package manager has it. Uninstall it first +> using whichever tool you originally used: > > ```bash -> pip uninstall bedrock-agentcore-starter-toolkit +> pip uninstall bedrock-agentcore-starter-toolkit # if installed via pip +> pipx uninstall bedrock-agentcore-starter-toolkit # if installed via pipx +> uv tool uninstall bedrock-agentcore-starter-toolkit # if installed via uv > ``` +> +> If you need to bypass the check (for example, in CI), set `AGENTCORE_SKIP_CONFLICT_CHECK=1` before installing. + +```bash +npm install -g @aws/agentcore +``` ## Quick Start diff --git a/e2e-tests/e2e-helper.ts b/e2e-tests/e2e-helper.ts index 0ec88fb6..8eb9e28d 100644 --- a/e2e-tests/e2e-helper.ts +++ b/e2e-tests/e2e-helper.ts @@ -121,24 +121,35 @@ export function createE2ESuite(cfg: E2EConfig) { if (testDir) await rm(testDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 1000 }); }, 600000); + // Container builds go through CodeBuild which is slower and more prone to transient failures. + const isContainerBuild = cfg.build === 'Container'; + const deployRetries = isContainerBuild ? 3 : 1; + const deployTimeout = isContainerBuild ? 900000 : 600000; + it.skipIf(!canRun)( 'deploys to AWS successfully', async () => { expect(projectPath, 'Project should have been created').toBeTruthy(); - const result = await runCLI(['deploy', '--yes', '--json'], projectPath, false); + await retry( + async () => { + const result = await runCLI(['deploy', '--yes', '--json'], projectPath, false); - if (result.exitCode !== 0) { - console.log('Deploy stdout:', result.stdout); - console.log('Deploy stderr:', result.stderr); - } + if (result.exitCode !== 0) { + console.log('Deploy stdout:', result.stdout); + console.log('Deploy stderr:', result.stderr); + } - expect(result.exitCode, `Deploy failed: ${result.stderr}`).toBe(0); + expect(result.exitCode, `Deploy failed (stderr: ${result.stderr}, stdout: ${result.stdout})`).toBe(0); - const json = parseJsonOutput(result.stdout) as { success: boolean }; - expect(json.success, 'Deploy should report success').toBe(true); + const json = parseJsonOutput(result.stdout) as { success: boolean }; + expect(json.success, 'Deploy should report success').toBe(true); + }, + deployRetries, + 30000 + ); }, - 600000 + deployTimeout ); it.skipIf(!canRun)( diff --git a/package-lock.json b/package-lock.json index 253c9aa2..ed36c6fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,7 +47,7 @@ "@typescript-eslint/parser": "^8.50.0", "@vitest/coverage-v8": "^4.0.18", "@xterm/headless": "^6.0.0", - "aws-cdk-lib": "^2.240.0", + "aws-cdk-lib": "^2.243.0", "constructs": "^10.4.4", "esbuild": "^0.27.2", "eslint": "^9.39.4", @@ -73,7 +73,7 @@ "node": ">=20" }, "peerDependencies": { - "aws-cdk-lib": "^2.234.1", + "aws-cdk-lib": "^2.243.0", "constructs": "^10.0.0" } }, @@ -2671,13 +2671,13 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.10", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.10.tgz", - "integrity": "sha512-OnejAIVD+CxzyAUrVic7lG+3QRltyja9LoNqCE/1YVs8ichoTbJlVSaZ9iSMcnHLyzrSNtvaOGjSDRP+d/ouFA==", + "version": "3.972.14", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.14.tgz", + "integrity": "sha512-G/Yd8Bnnyh8QrqLf8jWJbixEnScUFW24e/wOBGYdw1Cl4r80KX/DvHyM2GVZ2vTp7J4gTEr8IXJlTadA8+UfuQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0", - "fast-xml-parser": "5.4.1", + "@smithy/types": "^4.13.1", + "fast-xml-parser": "5.5.6", "tslib": "^2.6.2" }, "engines": { @@ -4986,9 +4986,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.0.tgz", - "integrity": "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -11638,9 +11638,9 @@ } }, "node_modules/path-expression-matcher": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.1.3.tgz", - "integrity": "sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.2.0.tgz", + "integrity": "sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==", "funding": [ { "type": "github", @@ -13146,9 +13146,9 @@ } }, "node_modules/strnum": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.0.tgz", - "integrity": "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.1.tgz", + "integrity": "sha512-BwRvNd5/QoAtyW1na1y1LsJGQNvRlkde6Q/ipqqEaivoMdV+B1OMOTVdwR+N/cwVUcIt9PYyHmV8HyexCZSupg==", "funding": [ { "type": "github", diff --git a/package.json b/package.json index 6354176c..4ce2d0e0 100644 --- a/package.json +++ b/package.json @@ -132,11 +132,13 @@ }, "overridesComments": { "minimatch": "GHSA-7r86-cg39-jmmj, GHSA-23c5-xmqv-rm74: minimatch 10.0.0-10.2.2 has ReDoS vulnerabilities. Multiple transitive deps (eslint, typescript-eslint, eslint-plugin-import, eslint-plugin-react, prettier-plugin-sort-imports, aws-cdk-lib) pin older versions. Remove this override once upstream packages update their minimatch dependency to >=10.2.3.", - "fast-xml-parser": "GHSA-8gc5-j5rx-235r, GHSA-jp2q-39xq-3w4g: fast-xml-parser <=5.5.6 has entity expansion bypass (CVE-2026-33036, CVE-2026-33349). Transitive via @aws-sdk/xml-builder. Remove once @aws-sdk updates to fast-xml-parser >=5.5.7." + "fast-xml-parser": "GHSA-8gc5-j5rx-235r, GHSA-jp2q-39xq-3w4g: fast-xml-parser <=5.5.6 has entity expansion bypass (CVE-2026-33036, CVE-2026-33349). Transitive via @aws-sdk/xml-builder. Remove once @aws-sdk updates to fast-xml-parser >=5.5.7.", + "@aws-sdk/xml-builder": "@aws-sdk/xml-builder <3.972.14 uses fast-xml-parser without configuring processEntities.maxTotalExpansions, so the 5.5.7 default of 1000 breaks large CloudFormation responses. 3.972.14+ sets maxTotalExpansions: Infinity (safe for trusted AWS API responses). Remove once @aws-sdk/core updates to >=3.973.22." }, "overrides": { "minimatch": "10.2.4", - "fast-xml-parser": "5.5.7" + "fast-xml-parser": "5.5.7", + "@aws-sdk/xml-builder": "3.972.14" }, "engines": { "node": ">=20" diff --git a/scripts/check-old-cli.lib.mjs b/scripts/check-old-cli.lib.mjs new file mode 100644 index 00000000..3401d8d7 --- /dev/null +++ b/scripts/check-old-cli.lib.mjs @@ -0,0 +1,104 @@ +/** + * Testable detection logic for the old Bedrock AgentCore Starter Toolkit. + * + * Each function accepts an `execSyncFn` so callers can inject a mock. + */ + +const INSTALLERS = [ + { cmd: 'pip list', label: 'pip', uninstallCmd: 'pip uninstall bedrock-agentcore-starter-toolkit' }, + { cmd: 'pipx list', label: 'pipx', uninstallCmd: 'pipx uninstall bedrock-agentcore-starter-toolkit' }, + { cmd: 'uv tool list', label: 'uv', uninstallCmd: 'uv tool uninstall bedrock-agentcore-starter-toolkit' }, +]; + +/** + * Run a package-manager list command and check whether the old toolkit appears. + * Returns `{ installer, uninstallCmd }` when found, or `null`. + */ +export function probeInstaller(cmd, label, uninstallCmd, execSyncFn) { + try { + const output = execSyncFn(cmd); + if (/^bedrock-agentcore-starter-toolkit\s/m.test(output)) { + return { installer: label, uninstallCmd }; + } + } catch { + // Command not found or non-zero exit — ignore. + } + return null; +} + +/** + * PATH-based fallback: locate an `agentcore` binary and check whether it's + * the old Python CLI (which doesn't support --version). + * Returns `{ installer, uninstallCmd }` when the old CLI is found, or `null`. + */ +export function probePath(execSyncFn, platform = process.platform) { + const whichCmd = platform === 'win32' ? 'where agentcore' : 'command -v agentcore'; + let binaryPath; + try { + binaryPath = execSyncFn(whichCmd).trim(); + } catch { + return null; // no agentcore binary on PATH + } + // Skip binaries installed via npm/node — a broken new CLI install would also + // fail --version, and we don't want to block reinstallation. + if (/node_modules|\/npm\/|\/nvm\/|\/fnm\/|\\npm\\/.test(binaryPath)) { + return null; + } + try { + execSyncFn('agentcore --version'); + return null; // --version succeeded — this is the new CLI + } catch { + // --version failed — likely the old Python CLI + return { + installer: 'PATH', + uninstallCmd: 'pip uninstall bedrock-agentcore-starter-toolkit', + }; + } +} + +/** + * Probe pip, pipx, and uv for the old toolkit, then fall back to PATH-based + * detection. Returns an array of matches. + */ +export function detectOldToolkit(execSyncFn) { + const results = []; + for (const { cmd, label, uninstallCmd } of INSTALLERS) { + const match = probeInstaller(cmd, label, uninstallCmd, execSyncFn); + if (match) results.push(match); + } + // If package-manager queries found nothing, fall back to PATH-based check + if (results.length === 0) { + const pathMatch = probePath(execSyncFn); + if (pathMatch) results.push(pathMatch); + } + return results; +} + +/** + * Format a user-facing error message listing per-installer uninstall commands. + */ +export function formatErrorMessage(detected) { + const lines = [ + '', + '\x1b[31mError: The old Bedrock AgentCore Starter Toolkit is installed and conflicts with @aws/agentcore.\x1b[0m', + '', + 'Uninstall it first, then re-run the install:', + '', + ]; + + for (const { installer, uninstallCmd } of detected) { + lines.push(` ${uninstallCmd} # installed via ${installer}`); + } + + lines.push( + '', + 'Then re-run:', + '', + ' npm install -g @aws/agentcore', + '', + 'To bypass this check, set AGENTCORE_SKIP_CONFLICT_CHECK=1', + '' + ); + + return lines.join('\n'); +} diff --git a/scripts/check-old-cli.mjs b/scripts/check-old-cli.mjs index 8a1b28b2..8dc7db52 100644 --- a/scripts/check-old-cli.mjs +++ b/scripts/check-old-cli.mjs @@ -1,26 +1,11 @@ +import { detectOldToolkit, formatErrorMessage } from './check-old-cli.lib.mjs'; import { execSync } from 'node:child_process'; -import { platform } from 'node:os'; -try { - // Check if an `agentcore` binary exists on PATH - const whichCmd = platform() === 'win32' ? 'where agentcore' : 'command -v agentcore'; - execSync(whichCmd, { stdio: 'ignore' }); +if (process.env.AGENTCORE_SKIP_CONFLICT_CHECK === '1') process.exit(0); - // Binary exists — check if it supports --version (the new CLI does, the old Python one does not) - try { - execSync('agentcore --version', { stdio: 'ignore' }); - } catch { - // --version failed → likely the old Python CLI - console.warn( - [ - '', - '\x1b[33m⚠ WARNING: We detected an older version of the AgentCore CLI.\x1b[0m', - '\x1b[33mFor the best experience, we recommend uninstalling it using:\x1b[0m', - '\x1b[33m pip uninstall bedrock-agentcore-starter-toolkit\x1b[0m', - '', - ].join('\n') - ); - } -} catch { - // No agentcore binary found or unexpected error — nothing to do +const detected = detectOldToolkit(cmd => execSync(cmd, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] })); + +if (detected.length > 0) { + console.error(formatErrorMessage(detected)); + process.exit(1); } diff --git a/src/cli/external-requirements/__tests__/check-old-cli.test.ts b/src/cli/external-requirements/__tests__/check-old-cli.test.ts new file mode 100644 index 00000000..86a1c656 --- /dev/null +++ b/src/cli/external-requirements/__tests__/check-old-cli.test.ts @@ -0,0 +1,279 @@ +import { + detectOldToolkit, + formatErrorMessage, + probeInstaller, + probePath, +} from '../../../../scripts/check-old-cli.lib.mjs'; +import { execSync } from 'node:child_process'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { describe, expect, it } from 'vitest'; + +// --------------------------------------------------------------------------- +// probeInstaller +// --------------------------------------------------------------------------- +describe('probeInstaller', () => { + it('returns match when output contains the old toolkit', () => { + const exec = () => 'bedrock-agentcore-starter-toolkit 0.1.0\nsome-other-pkg 1.0.0'; + const result = probeInstaller('pip list', 'pip', 'pip uninstall bedrock-agentcore-starter-toolkit', exec); + expect(result).toEqual({ + installer: 'pip', + uninstallCmd: 'pip uninstall bedrock-agentcore-starter-toolkit', + }); + }); + + it('returns null when the old toolkit is not in output', () => { + const exec = () => 'some-other-pkg 1.0.0'; + const result = probeInstaller('pip list', 'pip', 'pip uninstall bedrock-agentcore-starter-toolkit', exec); + expect(result).toBeNull(); + }); + + it('does not match a package whose name is a superstring of the toolkit', () => { + const exec = () => 'bedrock-agentcore-starter-toolkit-extra 1.0.0'; + const result = probeInstaller('pip list', 'pip', 'pip uninstall bedrock-agentcore-starter-toolkit', exec); + expect(result).toBeNull(); + }); + + it('returns null when the command throws', () => { + const exec = () => { + throw new Error('command not found'); + }; + const result = probeInstaller('pip list', 'pip', 'pip uninstall bedrock-agentcore-starter-toolkit', exec); + expect(result).toBeNull(); + }); +}); + +// --------------------------------------------------------------------------- +// probePath +// --------------------------------------------------------------------------- +describe('probePath', () => { + it('returns match when agentcore exists but --version fails (old Python CLI)', () => { + const exec = (cmd: string) => { + if (cmd === 'command -v agentcore') return '/usr/local/bin/agentcore'; + if (cmd === 'agentcore --version') throw new Error('exit code 1'); + return ''; + }; + const result = probePath(exec); + expect(result).toEqual({ + installer: 'PATH', + uninstallCmd: 'pip uninstall bedrock-agentcore-starter-toolkit', + }); + }); + + it('returns null when agentcore exists and --version succeeds (new CLI)', () => { + const exec = (cmd: string) => { + if (cmd === 'command -v agentcore') return '/usr/local/bin/agentcore'; + if (cmd === 'agentcore --version') return '1.0.0'; + return ''; + }; + expect(probePath(exec)).toBeNull(); + }); + + it('returns null when no agentcore binary is on PATH', () => { + const exec = () => { + throw new Error('command not found'); + }; + expect(probePath(exec)).toBeNull(); + }); + + it('uses "where agentcore" on Windows', () => { + const calls: string[] = []; + const exec = (cmd: string) => { + calls.push(cmd); + if (cmd === 'where agentcore') return 'C:\\Python\\Scripts\\agentcore'; + if (cmd === 'agentcore --version') throw new Error('exit code 1'); + return ''; + }; + const result = probePath(exec, 'win32'); + expect(calls[0]).toBe('where agentcore'); + expect(result).toEqual({ + installer: 'PATH', + uninstallCmd: 'pip uninstall bedrock-agentcore-starter-toolkit', + }); + }); + + it('returns null when binary is inside node_modules (broken new CLI)', () => { + const exec = (cmd: string) => { + if (cmd === 'command -v agentcore') return '/usr/local/lib/node_modules/@aws/agentcore/bin/agentcore'; + if (cmd === 'agentcore --version') throw new Error('exit code 1'); + return ''; + }; + expect(probePath(exec)).toBeNull(); + }); + + it('uses "command -v agentcore" on non-Windows', () => { + const calls: string[] = []; + const exec = (cmd: string) => { + calls.push(cmd); + if (cmd === 'command -v agentcore') return '/usr/local/bin/agentcore'; + if (cmd === 'agentcore --version') throw new Error('exit code 1'); + return ''; + }; + probePath(exec, 'linux'); + expect(calls[0]).toBe('command -v agentcore'); + }); +}); + +// --------------------------------------------------------------------------- +// detectOldToolkit +// --------------------------------------------------------------------------- +describe('detectOldToolkit', () => { + it('returns empty array when no installer has the old toolkit', () => { + const exec = () => 'some-pkg 1.0.0'; + expect(detectOldToolkit(exec)).toEqual([]); + }); + + it('returns single match for pip only', () => { + const exec = (cmd: string) => { + if (cmd === 'pip list') return 'bedrock-agentcore-starter-toolkit 0.1.0'; + return 'clean-output'; + }; + const result = detectOldToolkit(exec); + expect(result).toHaveLength(1); + expect(result[0]!.installer).toBe('pip'); + }); + + it('returns single match for pipx only', () => { + const exec = (cmd: string) => { + if (cmd === 'pipx list') return 'bedrock-agentcore-starter-toolkit 0.1.0'; + return 'clean-output'; + }; + const result = detectOldToolkit(exec); + expect(result).toHaveLength(1); + expect(result[0]!.installer).toBe('pipx'); + }); + + it('returns single match for uv only', () => { + const exec = (cmd: string) => { + if (cmd === 'uv tool list') return 'bedrock-agentcore-starter-toolkit 0.1.0'; + return 'clean-output'; + }; + const result = detectOldToolkit(exec); + expect(result).toHaveLength(1); + expect(result[0]!.installer).toBe('uv'); + }); + + it('returns multiple matches when installed via pip and pipx', () => { + const exec = () => 'bedrock-agentcore-starter-toolkit 0.1.0'; + const result = detectOldToolkit(exec); + expect(result).toHaveLength(3); + }); + + it('handles mixed results: one found, one missing command, one clean', () => { + const exec = (cmd: string) => { + if (cmd === 'pip list') return 'bedrock-agentcore-starter-toolkit 0.1.0'; + if (cmd === 'pipx list') throw new Error('command not found'); + return 'clean-output'; + }; + const result = detectOldToolkit(exec); + expect(result).toHaveLength(1); + expect(result[0]!.installer).toBe('pip'); + }); + + it('falls back to PATH detection when no package manager finds the toolkit', () => { + const exec = (cmd: string) => { + // All package-manager list commands return clean output + if (cmd.includes('list')) return 'clean-output'; + // PATH check: binary exists but --version fails + if (cmd === 'command -v agentcore') return '/usr/local/bin/agentcore'; + if (cmd === 'agentcore --version') throw new Error('exit code 1'); + return ''; + }; + const result = detectOldToolkit(exec); + expect(result).toHaveLength(1); + expect(result[0]!.installer).toBe('PATH'); + }); + + it('skips PATH fallback when a package manager already found the toolkit', () => { + const calls: string[] = []; + const exec = (cmd: string) => { + calls.push(cmd); + if (cmd === 'pip list') return 'bedrock-agentcore-starter-toolkit 0.1.0'; + return 'clean-output'; + }; + const result = detectOldToolkit(exec); + expect(result).toHaveLength(1); + expect(result[0]!.installer).toBe('pip'); + expect(calls).not.toContain('command -v agentcore'); + }); +}); + +// --------------------------------------------------------------------------- +// formatErrorMessage +// --------------------------------------------------------------------------- +describe('formatErrorMessage', () => { + it('shows correct uninstall command for a single installer', () => { + const msg = formatErrorMessage([ + { installer: 'pip', uninstallCmd: 'pip uninstall bedrock-agentcore-starter-toolkit' }, + ]); + expect(msg).toContain('pip uninstall bedrock-agentcore-starter-toolkit'); + expect(msg).toContain('installed via pip'); + }); + + it('shows all uninstall commands for multiple installers', () => { + const msg = formatErrorMessage([ + { installer: 'pip', uninstallCmd: 'pip uninstall bedrock-agentcore-starter-toolkit' }, + { installer: 'pipx', uninstallCmd: 'pipx uninstall bedrock-agentcore-starter-toolkit' }, + ]); + expect(msg).toContain('pip uninstall bedrock-agentcore-starter-toolkit'); + expect(msg).toContain('pipx uninstall bedrock-agentcore-starter-toolkit'); + }); + + it('contains bypass env var instruction', () => { + const msg = formatErrorMessage([ + { installer: 'pip', uninstallCmd: 'pip uninstall bedrock-agentcore-starter-toolkit' }, + ]); + expect(msg).toContain('AGENTCORE_SKIP_CONFLICT_CHECK=1'); + }); + + it('contains re-run instruction', () => { + const msg = formatErrorMessage([ + { installer: 'pip', uninstallCmd: 'pip uninstall bedrock-agentcore-starter-toolkit' }, + ]); + expect(msg).toContain('npm install -g @aws/agentcore'); + }); +}); + +// --------------------------------------------------------------------------- +// Entry-point integration (subprocess) +// --------------------------------------------------------------------------- +describe('check-old-cli.mjs entry point', () => { + const scriptPath = path.resolve(__dirname, '../../../../scripts/check-old-cli.mjs'); + + it('exits 0 when AGENTCORE_SKIP_CONFLICT_CHECK=1', () => { + // Should not throw (exit code 0) + execSync(`node ${scriptPath}`, { + env: { ...process.env, AGENTCORE_SKIP_CONFLICT_CHECK: '1' }, + stdio: 'pipe', + }); + }); + + it('exits 1 with actionable error when old toolkit is detected', () => { + // Use a wrapper script that stubs pip to report the old toolkit, guaranteeing + // the exit-1 path is always exercised regardless of the test machine. + const scriptsDir = path.resolve(__dirname, '../../../../scripts'); + const wrapperPath = path.join(scriptsDir, '_test-stub-detect.mjs'); + fs.writeFileSync( + wrapperPath, + [ + `import { detectOldToolkit, formatErrorMessage } from './check-old-cli.lib.mjs';`, + `const detected = detectOldToolkit((cmd) => {`, + ` if (cmd === 'pip list') return 'bedrock-agentcore-starter-toolkit 0.1.0';`, + ` throw new Error('not found');`, + `});`, + `if (detected.length > 0) { console.error(formatErrorMessage(detected)); process.exit(1); }`, + ].join('\n') + ); + try { + execSync(`node ${wrapperPath}`, { stdio: 'pipe', encoding: 'utf-8' }); + expect.unreachable('Should have exited with code 1'); + } catch (err: any) { + expect(err.status).toBe(1); + expect(err.stderr).toContain('bedrock-agentcore-starter-toolkit'); + expect(err.stderr).toContain('AGENTCORE_SKIP_CONFLICT_CHECK'); + expect(err.stderr).toContain('pip uninstall'); + } finally { + fs.unlinkSync(wrapperPath); + } + }); +});