Skip to content

Commit b1135be

Browse files
fix(packaging): preserve top-level agentcore Python package in zip (#1424)
PR #1015 removed a stale CONFIG_DIR exclusion from the zip-stage collectFiles helpers because the zip operates on the staging directory, not the project root, and the project config dir is filtered out at the copy stage. PR #1068 reintroduced the check via a merge resolution that took the wrong side. Any Python package whose top-level module is named agentcore (i.e. staging/agentcore/) gets silently dropped from the deployment artifact on main. Restore the PR #1015 state: drop the CONFIG_DIR check from collectFiles and collectFilesSync and drop the now-unused rootDir parameter and call sites. Existing copy-stage exclusion for the project config dir is unaffected. Add zip-stage regression tests under issue #1408. Closes #1408
1 parent 38f7144 commit b1135be

2 files changed

Lines changed: 51 additions & 29 deletions

File tree

src/lib/packaging/__tests__/helpers.test.ts

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -475,44 +475,68 @@ describe('nested agentcore directory is preserved (issue #843)', () => {
475475
expect(existsSync(join(dest, 'lib', 'langgraph_checkpoint_aws', 'agentcore', '__init__.py'))).toBe(true);
476476
expect(existsSync(join(dest, 'lib', 'langgraph_checkpoint_aws', 'agentcore', 'core.py'))).toBe(true);
477477
});
478+
});
479+
480+
// ── Issue #1408: zip stage must not drop top-level agentcore Python packages ──
481+
482+
describe('top-level agentcore Python package is preserved in zip (issue #1408)', () => {
483+
let root: string;
484+
485+
beforeAll(() => {
486+
root = mkdtempSync(join(tmpdir(), 'helpers-zip-agentcore-pkg-'));
487+
});
478488

479-
// ── createZipFromDir (async) ──
489+
afterAll(() => {
490+
rmSync(root, { recursive: true, force: true });
491+
});
480492

481-
it('zip: excludes top-level agentcore/ but includes nested agentcore/', async () => {
482-
const src = buildFixture(join(root, 'zip-async'));
493+
/**
494+
* Mimics a staging directory after `uv pip install --target staging`
495+
* has installed a third-party package whose top-level module happens to
496+
* be named `agentcore`. The zip stage runs against staging, where the
497+
* project's own `agentcore/` config dir is already absent, so a
498+
* top-level `agentcore/` here is a real Python package and must be
499+
* included in the deployment artifact.
500+
*/
501+
function buildStagingFixture(base: string): string {
502+
const staging = join(base, 'staging');
503+
504+
mkdirSync(staging, { recursive: true });
505+
writeFileSync(join(staging, 'main.py'), 'print("hello")');
506+
507+
// Top-level `agentcore` Python package (e.g. an installed dependency)
508+
mkdirSync(join(staging, 'agentcore'), { recursive: true });
509+
writeFileSync(join(staging, 'agentcore', '__init__.py'), '# package init');
510+
writeFileSync(join(staging, 'agentcore', 'runtime.py'), 'def run(): pass');
511+
512+
return staging;
513+
}
514+
515+
it('createZipFromDir includes a top-level agentcore Python package', async () => {
516+
const staging = buildStagingFixture(join(root, 'zip-async'));
483517
const zipPath = join(root, 'zip-async.zip');
484518

485-
await createZipFromDir(src, zipPath);
519+
await createZipFromDir(staging, zipPath);
486520

487521
const zipBuffer = await readFile(zipPath);
488522
const entries = Object.keys(unzipSync(new Uint8Array(zipBuffer)));
489523

490-
// Top-level agentcore/ should NOT appear
491-
expect(entries.some(e => e === 'agentcore/config.yaml')).toBe(false);
492-
expect(entries.some(e => e.startsWith('agentcore/'))).toBe(false);
493-
494-
// Nested agentcore/ SHOULD appear
495-
expect(entries).toContain('lib/langgraph_checkpoint_aws/agentcore/__init__.py');
496-
expect(entries).toContain('lib/langgraph_checkpoint_aws/agentcore/core.py');
497-
498-
// Regular files present
524+
expect(entries).toContain('agentcore/__init__.py');
525+
expect(entries).toContain('agentcore/runtime.py');
499526
expect(entries).toContain('main.py');
500527
});
501528

502-
// ── createZipFromDirSync ──
503-
504-
it('sync zip: excludes top-level agentcore/ but includes nested agentcore/', () => {
505-
const src = buildFixture(join(root, 'zip-sync'));
529+
it('createZipFromDirSync includes a top-level agentcore Python package', () => {
530+
const staging = buildStagingFixture(join(root, 'zip-sync'));
506531
const zipPath = join(root, 'zip-sync.zip');
507532

508-
createZipFromDirSync(src, zipPath);
533+
createZipFromDirSync(staging, zipPath);
509534

510535
const zipBuffer = readFileSync(zipPath);
511536
const entries = Object.keys(unzipSync(new Uint8Array(zipBuffer)));
512537

513-
expect(entries.some(e => e.startsWith('agentcore/'))).toBe(false);
514-
expect(entries).toContain('lib/langgraph_checkpoint_aws/agentcore/__init__.py');
515-
expect(entries).toContain('lib/langgraph_checkpoint_aws/agentcore/core.py');
538+
expect(entries).toContain('agentcore/__init__.py');
539+
expect(entries).toContain('agentcore/runtime.py');
516540
expect(entries).toContain('main.py');
517541
});
518542
});

src/lib/packaging/helpers.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -192,24 +192,23 @@ export async function createZipFromDir(sourceDir: string, outputZip: string): Pr
192192
await rm(outputZip, { force: true });
193193
await mkdir(dirname(outputZip), { recursive: true });
194194

195-
const files = await collectFiles(sourceDir, sourceDir);
195+
const files = await collectFiles(sourceDir);
196196
const zipped = zipSync(files);
197197
await writeFile(outputZip, zipped);
198198
}
199199

200-
async function collectFiles(directory: string, rootDir: string, basePath = ''): Promise<Zippable> {
200+
async function collectFiles(directory: string, basePath = ''): Promise<Zippable> {
201201
const result: Zippable = {};
202202
const entries = await readdir(directory, { withFileTypes: true });
203203

204204
for (const entry of entries) {
205205
if (EXCLUDED_ENTRIES.has(entry.name)) continue;
206-
if (entry.name === CONFIG_DIR && resolve(directory) === resolve(rootDir)) continue;
207206

208207
const fullPath = join(directory, entry.name);
209208
const zipPath = basePath ? `${basePath}/${entry.name}` : entry.name;
210209

211210
if (entry.isDirectory()) {
212-
Object.assign(result, await collectFiles(fullPath, rootDir, zipPath));
211+
Object.assign(result, await collectFiles(fullPath, zipPath));
213212
} else if (entry.isFile()) {
214213
result[zipPath] = [await readFile(fullPath), { level: 6 }];
215214
}
@@ -398,19 +397,18 @@ export function ensureBinaryAvailableSync(binary: string, installHint?: string):
398397
throw new MissingDependencyError(binary, installHint);
399398
}
400399

401-
function collectFilesSync(directory: string, rootDir: string, basePath = ''): Zippable {
400+
function collectFilesSync(directory: string, basePath = ''): Zippable {
402401
const result: Zippable = {};
403402
const entries = readdirSync(directory, { withFileTypes: true });
404403

405404
for (const entry of entries) {
406405
if (EXCLUDED_ENTRIES.has(entry.name)) continue;
407-
if (entry.name === CONFIG_DIR && resolve(directory) === resolve(rootDir)) continue;
408406

409407
const fullPath = join(directory, entry.name);
410408
const zipPath = basePath ? `${basePath}/${entry.name}` : entry.name;
411409

412410
if (entry.isDirectory()) {
413-
Object.assign(result, collectFilesSync(fullPath, rootDir, zipPath));
411+
Object.assign(result, collectFilesSync(fullPath, zipPath));
414412
} else if (entry.isFile()) {
415413
result[zipPath] = [readFileSync(fullPath), { level: 6 }];
416414
}
@@ -422,7 +420,7 @@ export function createZipFromDirSync(sourceDir: string, outputZip: string): void
422420
rmSync(outputZip, { force: true });
423421
mkdirSync(dirname(outputZip), { recursive: true });
424422

425-
const files = collectFilesSync(sourceDir, sourceDir);
423+
const files = collectFilesSync(sourceDir);
426424
const zipped = zipSync(files);
427425
writeFileSync(outputZip, zipped);
428426
}

0 commit comments

Comments
 (0)