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", () => { 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", () => {