diff --git a/bin/gstack-gbrain-sync.ts b/bin/gstack-gbrain-sync.ts index 61d9e677f..a3071337d 100644 --- a/bin/gstack-gbrain-sync.ts +++ b/bin/gstack-gbrain-sync.ts @@ -287,13 +287,20 @@ function gbrainSupportsSourcesRename(env?: NodeJS.ProcessEnv): boolean { * `env` is the environment passed to the spawned `gbrain` process; defaults * to `process.env`. Tests inject a PATH that points at a gbrain shim so the * helper can be exercised without a real gbrain CLI. + * + * Shape note: `gbrain sources list --json` returns `{sources: [...]}` (v0.20+); + * older versions returned a flat array. Accept both for forward/backward compat + * (mirrors `probeSource`/`sourcePageCount` in lib/gbrain-sources.ts). */ export function sourceLocalPath(sourceId: string, env?: NodeJS.ProcessEnv): string | null { - const list = execGbrainJson>( + const raw = execGbrainJson( ["sources", "list", "--json"], { baseEnv: env }, ); - if (!list) return null; + if (!raw) return null; + const list: Array<{ id?: string; local_path?: string }> = Array.isArray(raw) + ? (raw as Array<{ id?: string; local_path?: string }>) + : ((raw as { sources?: Array<{ id?: string; local_path?: string }> }).sources ?? []); const found = list.find((s) => s.id === sourceId); return found?.local_path ?? null; } diff --git a/test/gstack-gbrain-sync.test.ts b/test/gstack-gbrain-sync.test.ts index 0f1edec21..19a9bac4e 100644 --- a/test/gstack-gbrain-sync.test.ts +++ b/test/gstack-gbrain-sync.test.ts @@ -837,4 +837,29 @@ describe("sourceLocalPath", () => { }); expect(sourceLocalPath("any-id", envWithBindir(bindir))).toBeNull(); }); + + // gbrain v0.20+ wraps the response as `{sources: [...]}`. Older versions + // returned a flat array. sourceLocalPath was returning null (or crashing + // with `list.find is not a function` upstream) because it only handled + // the flat-array shape. Pin both shapes here. + it("handles {sources: [...]} wrapped shape (gbrain v0.20+)", () => { + makeShim(bindir, { + "sources list --json": { + stdout: JSON.stringify({ + sources: [ + { id: "other-source", local_path: "/x" }, + { id: "target-id", local_path: "/repo/match" }, + ], + }), + }, + }); + expect(sourceLocalPath("target-id", envWithBindir(bindir))).toBe("/repo/match"); + }); + + it("returns null when the source is missing in the wrapped shape", () => { + makeShim(bindir, { + "sources list --json": { stdout: JSON.stringify({ sources: [] }) }, + }); + expect(sourceLocalPath("missing-id", envWithBindir(bindir))).toBeNull(); + }); });