Skip to content

Commit c9cc1f2

Browse files
feat: bundle @aws/agentcore-cdk inside CLI tarball for testing (#731)
* feat: bundle @aws/agentcore-cdk inside CLI tarball Bundle the L3 CDK constructs package directly into the CLI's distributed assets so a single tarball install gives users everything they need — no separate npm install of @aws/agentcore-cdk required. - copy-assets.mjs: copies pre-built agentcore-cdk into dist/assets/bundled-agentcore-cdk/ - Vended CDK package.json: uses file:../.bundled/agentcore-cdk dependency - CDKRenderer: copies bundled package into project during agentcore create Constraint: L3 repo must be built before CLI build (npm run build in sibling dir) Constraint: AGENTCORE_CDK_PATH env var overrides default sibling resolution Rejected: Embedding as a direct CLI dependency | CDK constructs are only used in vended projects, not by CLI runtime Confidence: high Scope-risk: moderate Not-tested: offline installs where npm registry is unreachable * feat: add npm run bundle command for single-command packaging Adds scripts/bundle.mjs that orchestrates the full build pipeline: 1. Finds CDK constructs via AGENTCORE_CDK_PATH, sibling dir, or clones latest from GitHub 2. Installs and builds CDK constructs 3. Installs and builds CLI (auto-bundles CDK) 4. Runs npm pack to produce the final tarball * refactor: make CDK bundling non-intrusive to default build/deploy flow The bundle script (npm run bundle) is now purely additive: - Normal build: no CDK tarball in dist/assets, no behavior change - Bundle build: CDK tarball placed in dist/assets/bundled-agentcore-cdk.tgz - CDKRenderer checks for bundled tarball after npm install and overrides the registry version only if present Reverts changes to copy-assets.mjs, vended package.json template, and snapshot to their original state. * feat: add timestamp-based versioning to bundle output Both CLI and CDK tarballs get a unique e2e.<timestamp> version suffix. Original versions are restored after packing so source files stay clean. * chore: remove e2e prefix from bundle version tag * fix: copy bundled CDK tarball locally before npm install Copies the tarball into the CDK project dir and installs from ./bundled-agentcore-cdk.tgz instead of an absolute path. This avoids npm writing ugly deep relative paths into package.json. The tarball is cleaned up after install. * fix: keep bundled CDK tarball in project for stable file: reference Keep bundled-agentcore-cdk.tgz in the CDK project dir so the file: reference in package.json stays valid for future npm install runs. * fix: crash-safe version restore and explicit git pull branch - Wrap npm pack calls in try/finally so package.json versions are always restored even if packing fails - Use git pull origin main instead of bare git pull * Potential fix for code scanning alert no. 22: Shell command built from environment values Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * fix: update remaining run() calls to use execFileSync signature The Copilot autofix changed run() from execSync(string) to execFileSync(cmd, args[]), but only updated the git calls. The 6 npm calls still used the old signature and would fail at runtime. --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
1 parent dbb9c00 commit c9cc1f2

4 files changed

Lines changed: 204 additions & 1 deletion

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,6 @@ ProtocolTesting/
6464

6565
# Git worktrees
6666
.worktrees/
67+
68+
# Auto-cloned CDK constructs (from scripts/bundle.mjs)
69+
.cdk-constructs-clone/

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@
6868
"test:unit": "vitest run --project unit --coverage",
6969
"test:e2e": "vitest run --project e2e",
7070
"test:update-snapshots": "vitest run --project unit --update",
71-
"test:tui": "npm run build:harness && vitest run --project tui"
71+
"test:tui": "npm run build:harness && vitest run --project tui",
72+
"bundle": "node scripts/bundle.mjs"
7273
},
7374
"dependencies": {
7475
"@aws-cdk/toolkit-lib": "^1.16.0",

scripts/bundle.mjs

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/**
2+
* bundle.mjs — Single command to build CLI + CDK constructs into one tarball.
3+
*
4+
* This is a testing-only workflow. It does NOT modify the default build or
5+
* deployment flow. The normal `npm run build` + `npm pack` pipeline is unchanged.
6+
*
7+
* What this script does differently: after building both packages normally, it
8+
* packs the CDK constructs into a tarball and places it in the CLI's dist/assets/.
9+
* At `agentcore create` time, CDKRenderer detects this tarball and installs it
10+
* after the normal `npm install`, overriding the registry version.
11+
*
12+
* Usage:
13+
* node scripts/bundle.mjs
14+
* npm run bundle
15+
*
16+
* Environment variables:
17+
* AGENTCORE_CDK_PATH — absolute path to the agentcore-l3-cdk-constructs repo
18+
*/
19+
import { execFileSync } from 'node:child_process';
20+
import * as fs from 'node:fs';
21+
import * as path from 'node:path';
22+
import { fileURLToPath } from 'node:url';
23+
24+
const __filename = fileURLToPath(import.meta.url);
25+
const __dirname = path.dirname(__filename);
26+
const cliRoot = path.resolve(__dirname, '..');
27+
28+
const CDK_REPO_URL = 'https://github.com/aws/agentcore-l3-cdk-constructs.git';
29+
30+
function log(msg) {
31+
console.log(`\n[bundle] ${msg}`);
32+
}
33+
34+
function run(cmd, args = [], opts = {}) {
35+
const display = [cmd, ...args].join(' ');
36+
console.log(` > ${display}`);
37+
execFileSync(cmd, args, { stdio: 'inherit', ...opts });
38+
}
39+
40+
/**
41+
* Resolve the CDK constructs repo path. Priority:
42+
* 1. AGENTCORE_CDK_PATH env var
43+
* 2. Sibling directory ../agentcore-l3-cdk-constructs
44+
* 3. Clone from GitHub into a temp directory under the CLI repo
45+
*/
46+
function resolveCdkPath() {
47+
// 1. Env var
48+
if (process.env.AGENTCORE_CDK_PATH) {
49+
const p = path.resolve(process.env.AGENTCORE_CDK_PATH);
50+
if (fs.existsSync(path.join(p, 'package.json'))) {
51+
log(`Using CDK constructs from AGENTCORE_CDK_PATH: ${p}`);
52+
return p;
53+
}
54+
console.warn(` WARNING: AGENTCORE_CDK_PATH=${p} does not contain package.json, ignoring.`);
55+
}
56+
57+
// 2. Sibling directory
58+
const sibling = path.resolve(cliRoot, '..', 'agentcore-l3-cdk-constructs');
59+
if (fs.existsSync(path.join(sibling, 'package.json'))) {
60+
log(`Using CDK constructs from sibling directory: ${sibling}`);
61+
return sibling;
62+
}
63+
64+
// 3. Clone latest from GitHub
65+
const cloneDir = path.join(cliRoot, '.cdk-constructs-clone');
66+
log(`CDK constructs repo not found locally. Cloning latest from GitHub...`);
67+
68+
if (fs.existsSync(cloneDir)) {
69+
log('Pulling latest changes...');
70+
run('git', ['pull', 'origin', 'main'], { cwd: cloneDir });
71+
} else {
72+
run('git', ['clone', '--depth', '1', CDK_REPO_URL, cloneDir]);
73+
}
74+
75+
return cloneDir;
76+
}
77+
78+
// ---------------------------------------------------------------------------
79+
// Main
80+
// ---------------------------------------------------------------------------
81+
82+
log('Starting bundle process...');
83+
84+
const timestamp = Math.floor(Date.now() / 1000);
85+
log(`Bundle timestamp: ${timestamp}`);
86+
87+
// Helper to bump a package version with a unique e2e timestamp tag.
88+
// Saves the original version so it can be restored after packing.
89+
function bumpVersion(pkgDir) {
90+
const pkgJsonPath = path.join(pkgDir, 'package.json');
91+
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
92+
const originalVersion = pkg.version;
93+
const baseVersion = originalVersion.split('-')[0];
94+
pkg.version = `${baseVersion}-${timestamp}`;
95+
fs.writeFileSync(pkgJsonPath, JSON.stringify(pkg, null, 2) + '\n');
96+
log(`Bumped ${pkg.name} version: ${originalVersion} -> ${pkg.version}`);
97+
return { pkgJsonPath, originalVersion, bumpedVersion: pkg.version };
98+
}
99+
100+
function restoreVersion({ pkgJsonPath, originalVersion }) {
101+
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
102+
pkg.version = originalVersion;
103+
fs.writeFileSync(pkgJsonPath, JSON.stringify(pkg, null, 2) + '\n');
104+
}
105+
106+
// Step 1: Resolve and build CDK constructs
107+
const cdkPath = resolveCdkPath();
108+
109+
log('Installing CDK constructs dependencies...');
110+
run('npm', ['install'], { cwd: cdkPath });
111+
112+
log('Building CDK constructs...');
113+
run('npm', ['run', 'build'], { cwd: cdkPath });
114+
115+
// Step 2: Bump CDK version and pack into a tarball
116+
const cdkVersionInfo = bumpVersion(cdkPath);
117+
try {
118+
log('Packing CDK constructs...');
119+
run('npm', ['pack'], { cwd: cdkPath });
120+
} finally {
121+
restoreVersion(cdkVersionInfo);
122+
}
123+
124+
const cdkTarballName = `aws-agentcore-cdk-${cdkVersionInfo.bumpedVersion}.tgz`;
125+
const cdkTarballSrc = path.join(cdkPath, cdkTarballName);
126+
127+
if (!fs.existsSync(cdkTarballSrc)) {
128+
console.error(`ERROR: Expected CDK tarball at ${cdkTarballSrc} but not found.`);
129+
process.exit(1);
130+
}
131+
132+
// Step 3: Build CLI normally (no modifications to copy-assets)
133+
log('Installing CLI dependencies...');
134+
run('npm', ['install'], { cwd: cliRoot });
135+
136+
log('Building CLI...');
137+
run('npm', ['run', 'build'], { cwd: cliRoot });
138+
139+
// Step 4: Copy CDK tarball into dist/assets/ so CDKRenderer can detect it
140+
const bundledTarballDest = path.join(cliRoot, 'dist', 'assets', 'bundled-agentcore-cdk.tgz');
141+
fs.copyFileSync(cdkTarballSrc, bundledTarballDest);
142+
log(`Placed CDK tarball at ${bundledTarballDest}`);
143+
144+
// Step 5: Bump CLI version and pack into final tarball (includes the bundled CDK tarball)
145+
const cliVersionInfo = bumpVersion(cliRoot);
146+
try {
147+
log('Packing CLI tarball...');
148+
run('npm', ['pack'], { cwd: cliRoot });
149+
} finally {
150+
restoreVersion(cliVersionInfo);
151+
}
152+
153+
const cliTarballName = `aws-agentcore-${cliVersionInfo.bumpedVersion}.tgz`;
154+
const cliTarballPath = path.join(cliRoot, cliTarballName);
155+
156+
if (fs.existsSync(cliTarballPath)) {
157+
log(`Done! Tarball: ${cliTarballPath}`);
158+
log(`Install with: npm install ${cliTarballPath}`);
159+
log('When you run agentcore create, the bundled CDK constructs will be installed automatically.');
160+
} else {
161+
log(`Done! Check ${cliRoot} for the .tgz file.`);
162+
}

src/cli/templates/CDKRenderer.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ export class CDKRenderer {
8686
}
8787
logger?.logSubStep(`npm install completed (${(installDuration / 1000).toFixed(1)}s)`);
8888

89+
// If the CLI was built with a bundled CDK constructs tarball (via npm run bundle),
90+
// install it to override the registry version. This is a no-op for normal builds.
91+
await this.installBundledCdkIfPresent(outputDir, logger);
92+
8993
// Format the CDK project files
9094
logger?.logSubStep('Running npm run format...');
9195
logger?.logCommand('npm', ['run', 'format']);
@@ -116,6 +120,39 @@ export class CDKRenderer {
116120
await fs.copyFile(readmeSrc, readmeDest);
117121
}
118122

123+
private async installBundledCdkIfPresent(cdkProjectDir: string, logger?: CreateLogger): Promise<void> {
124+
const bundledTarball = path.join(this.assetsDir, 'bundled-agentcore-cdk.tgz');
125+
126+
try {
127+
await fs.access(bundledTarball);
128+
} catch {
129+
// No bundled tarball — normal build, nothing to do
130+
return;
131+
}
132+
133+
// Copy tarball into the CDK project so npm resolves a clean local path
134+
const localTarball = path.join(cdkProjectDir, 'bundled-agentcore-cdk.tgz');
135+
await fs.copyFile(bundledTarball, localTarball);
136+
137+
logger?.logSubStep('Installing bundled @aws/agentcore-cdk override...');
138+
const result = await runSubprocessCapture('npm', ['install', './bundled-agentcore-cdk.tgz'], {
139+
cwd: cdkProjectDir,
140+
});
141+
if (result.stdout) {
142+
logger?.logCommandOutput(result.stdout);
143+
}
144+
if (result.stderr) {
145+
logger?.logCommandOutput(result.stderr);
146+
}
147+
if (result.code !== 0) {
148+
throw new Error(`Failed to install bundled @aws/agentcore-cdk: ${result.stderr}`);
149+
}
150+
151+
// Keep the tarball in the project so the file: reference in package.json
152+
// stays valid for future npm install runs.
153+
logger?.logSubStep('Bundled @aws/agentcore-cdk installed');
154+
}
155+
119156
private async writeLlmContext(configDir: string): Promise<void> {
120157
const llmContextDir = path.join(configDir, LLM_CONTEXT_DIR);
121158
await fs.mkdir(llmContextDir, { recursive: true });

0 commit comments

Comments
 (0)