Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions bin/gstack-gbrain-sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,11 +289,17 @@ function gbrainSupportsSourcesRename(env?: NodeJS.ProcessEnv): boolean {
* helper can be exercised without a real gbrain CLI.
*/
export function sourceLocalPath(sourceId: string, env?: NodeJS.ProcessEnv): string | null {
const list = execGbrainJson<Array<{ id: string; local_path?: string }>>(
["sources", "list", "--json"],
{ baseEnv: env },
);
if (!list) return null;
// gbrain v0.30+ returns {"sources":[...]}; older releases returned a bare
// array. Handle both shapes so this works across the compat window. The
// sibling helper in lib/gbrain-sources.ts (statusForSourceId) already does
// this; the v1.40 hostname-fold migration was the lone holdout and crashed
// with `list.find is not a function` on every gbrain v0.30+ install.
const raw = execGbrainJson<
| Array<{ id: string; local_path?: string }>
| { sources?: Array<{ id: string; local_path?: string }> }
>(["sources", "list", "--json"], { baseEnv: env });
if (!raw) return null;
const list = Array.isArray(raw) ? raw : raw.sources || [];
const found = list.find((s) => s.id === sourceId);
return found?.local_path ?? null;
}
Expand Down
36 changes: 36 additions & 0 deletions test/gstack-gbrain-sync.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -837,4 +837,40 @@ describe("sourceLocalPath", () => {
});
expect(sourceLocalPath("any-id", envWithBindir(bindir))).toBeNull();
});

// Regression: gbrain v0.30+ wraps the list as {"sources":[...]} instead of
// returning a bare array. The previous implementation crashed with
// `list.find is not a function` on every current gbrain install, which
// broke `runCodeImport`'s hostname-fold migration on first sync after
// upgrading to gstack v1.40 + gbrain v0.30+. Both shapes must work to
// keep the compat window intact.
it("returns local_path when gbrain wraps the list as {sources: [...]} (v0.30+ shape)", () => {
makeShim(bindir, {
"sources list --json": {
stdout: JSON.stringify({
sources: [
{ id: "other-source", local_path: "/x" },
{ id: "target-id", local_path: "/repo/wrapped" },
],
}),
},
});
expect(sourceLocalPath("target-id", envWithBindir(bindir))).toBe("/repo/wrapped");
});

it("returns null when the wrapped {sources: [...]} shape has no matching id", () => {
makeShim(bindir, {
"sources list --json": {
stdout: JSON.stringify({ sources: [{ id: "other", local_path: "/x" }] }),
},
});
expect(sourceLocalPath("missing-id", envWithBindir(bindir))).toBeNull();
});

it("treats {sources: undefined} the same as an empty list", () => {
makeShim(bindir, {
"sources list --json": { stdout: JSON.stringify({}) },
});
expect(sourceLocalPath("any-id", envWithBindir(bindir))).toBeNull();
});
});