From f1452316343bff5fe07d12b2bfbbd97ce1c296f5 Mon Sep 17 00:00:00 2001 From: Kevin Date: Mon, 6 Jul 2026 11:38:44 -0400 Subject: [PATCH 1/2] fix(PROD-2277): create galleries folder on demand so a models-only sync doesn't ENOENT A models-only sync (--models / --elements="Models") skips downloading galleries (the PROD-2215 optimization), so agility-files//galleries is never created. But the push-side loader still reads it unconditionally: guid-data-loader -> getGalleriesFromFileSystem -> fileOperations.getFolderContents -> fs.readdirSync, which threw ENOENT and aborted the whole sync. Make getFolderContents create the folder and return [] when it is absent, matching the guards already present in readJsonFilesFromFolder/listFilesInFolder. Creating the folder (not just guarding the read) also restores the instance-folder scaffolding downstream steps assume. All four callers want "list files, empty if none", so auto-create is safe. Adds a regression test. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/core/fileOperations.ts | 8 ++++++++ src/core/tests/fileOperations.test.ts | 25 +++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/core/fileOperations.ts b/src/core/fileOperations.ts index 8b8fe323..a4fcf497 100644 --- a/src/core/fileOperations.ts +++ b/src/core/fileOperations.ts @@ -271,6 +271,14 @@ export class fileOperations { } getFolderContents(folder: string) { + // A scoped run (e.g. a models-only sync) legitimately skips downloading some + // element types, so their instance subfolder is never created. Create the + // folder on demand and return an empty listing instead of throwing ENOENT, + // matching how readJsonFilesFromFolder/listFilesInFolder already degrade. + if (!fs.existsSync(folder)) { + fs.mkdirSync(folder, { recursive: true }); + return []; + } return fs.readdirSync(folder); } diff --git a/src/core/tests/fileOperations.test.ts b/src/core/tests/fileOperations.test.ts index 44b8e8f9..e0a06eb0 100644 --- a/src/core/tests/fileOperations.test.ts +++ b/src/core/tests/fileOperations.test.ts @@ -251,6 +251,31 @@ describe("listFilesInFolder", () => { }); }); +// ─── getFolderContents ──────────────────────────────────────────────────────── + +describe("getFolderContents", () => { + it("lists the entries of an existing folder", () => { + const dir = path.join(tmpDir, "fc-guid", "galleries"); + fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync(path.join(dir, "1.json"), "{}"); + fs.writeFileSync(path.join(dir, "2.json"), "{}"); + + const ops = new fileOperations("fc-guid"); + expect(ops.getFolderContents(dir).sort()).toEqual(["1.json", "2.json"]); + }); + + it("creates the folder and returns [] when it does not exist (models-only sync regression)", () => { + const dir = path.join(tmpDir, "fc-missing-guid", "galleries"); + expect(fs.existsSync(dir)).toBe(false); + + const ops = new fileOperations("fc-missing-guid"); + expect(ops.getFolderContents(dir)).toEqual([]); + // The folder is created on demand so a scoped run that skipped downloading + // galleries does not crash later in the push pipeline with ENOENT. + expect(fs.existsSync(dir)).toBe(true); + }); +}); + // ─── saveMappingFile / getMappingFile ───────────────────────────────────────── describe("saveMappingFile / getMappingFile", () => { From a0a8f13741cae58beff0f574104d1f3ceb8f2b60 Mon Sep 17 00:00:00 2001 From: Kevin Date: Mon, 6 Jul 2026 13:15:26 -0400 Subject: [PATCH 2/2] test(PROD-2277): assert getGalleriesFromFileSystem returns [] on missing folder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This test previously asserted the old buggy behavior — that a missing galleries folder throws (the ENOENT that crashed a models-only sync). Update it to assert the fixed behavior: returns [], creates the folder, and does not throw. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../filesystem/tests/get-galleries.test.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/lib/getters/filesystem/tests/get-galleries.test.ts b/src/lib/getters/filesystem/tests/get-galleries.test.ts index c9b7c5b3..57709b87 100644 --- a/src/lib/getters/filesystem/tests/get-galleries.test.ts +++ b/src/lib/getters/filesystem/tests/get-galleries.test.ts @@ -34,10 +34,20 @@ function makeFileOps(subDir: string): fileOperations { } describe("getGalleriesFromFileSystem", () => { - it("throws or returns empty when galleries folder does not exist", () => { + it("returns empty and creates the folder when galleries folder does not exist", () => { const fileOps = makeFileOps("galleries-missing"); - // getFolderContents (readdirSync) throws when directory does not exist - expect(() => getGalleriesFromFileSystem(fileOps)).toThrow(); + const galleriesDir = path.join(fileOps.instancePath, "galleries"); + expect(fs.existsSync(galleriesDir)).toBe(false); + + // A models-only sync skips downloading galleries, so the folder is absent. + // getFolderContents creates it on demand and returns [] instead of throwing + // ENOENT, which previously crashed the push phase (PROD-2277). + let result: any; + expect(() => { + result = getGalleriesFromFileSystem(fileOps); + }).not.toThrow(); + expect(result).toEqual([]); + expect(fs.existsSync(galleriesDir)).toBe(true); }); it("returns an empty array when galleries folder is empty", () => {