diff --git a/.changeset/tolerate-gh-warnings.md b/.changeset/tolerate-gh-warnings.md new file mode 100644 index 0000000..e7a2d5e --- /dev/null +++ b/.changeset/tolerate-gh-warnings.md @@ -0,0 +1,5 @@ +--- +"@kitlangton/stack": patch +--- + +Tolerate non-JSON warnings (e.g. auth expiry notices) emitted before `gh pr view` JSON output. The decoder now extracts the JSON payload from stdout instead of requiring the entire output to be valid JSON. diff --git a/src/services/code-host/GitHub.ts b/src/services/code-host/GitHub.ts index 2d18f60..3209bc0 100644 --- a/src/services/code-host/GitHub.ts +++ b/src/services/code-host/GitHub.ts @@ -51,21 +51,31 @@ class PullListData extends Schema.Class("PullListData")({ const PullListJson = Schema.Array(Schema.Array(PullListData)); +const extractJson = (out: string): string => { + const start = out.search(/[[{]/); + if (start === -1) return out; + const opener = out[start]; + const closer = opener === "{" ? "}" : "]"; + const end = out.lastIndexOf(closer); + if (end === -1 || end < start) return out; + return out.slice(start, end + 1); +}; + const decodePullList = (args: ReadonlyArray, out: string) => Effect.try({ - try: () => Schema.decodeUnknownSync(PullListJson)(JSON.parse(out)), + try: () => Schema.decodeUnknownSync(PullListJson)(JSON.parse(extractJson(out))), catch: (err) => new CodeHostDecodeError("gh", args, out, String(err)), }); const decodePullView = (args: ReadonlyArray, out: string) => Effect.try({ - try: () => Schema.decodeUnknownSync(PullView)(JSON.parse(out)), + try: () => Schema.decodeUnknownSync(PullView)(JSON.parse(extractJson(out))), catch: (err) => new CodeHostDecodeError("gh", args, out, String(err)), }); const decodePullWatch = (args: ReadonlyArray, out: string) => Effect.try({ - try: () => Schema.decodeUnknownSync(PullWatch)(JSON.parse(out)), + try: () => Schema.decodeUnknownSync(PullWatch)(JSON.parse(extractJson(out))), catch: (err) => new CodeHostDecodeError("gh", args, out, String(err)), }); diff --git a/tests/stack.test.ts b/tests/stack.test.ts index 3285e2d..93fd795 100644 --- a/tests/stack.test.ts +++ b/tests/stack.test.ts @@ -1611,6 +1611,40 @@ describe("GitHub", () => { ); }); + it.effect("tolerates non-JSON warnings emitted before gh pr view output", () => { + const proc = Layer.succeed( + Proc.Service, + Proc.Service.of({ + exec: () => + Effect.sync(() => + [ + "warning: authentication token expires soon, run gh auth refresh", + JSON.stringify({ + number: 1, + title: "one", + body: "body", + headRefName: "one", + headRepository: { nameWithOwner: "owner/project" }, + baseRefName: "main", + url: "u1", + isDraft: false, + labels: [], + }), + ].join("\n"), + ), + }), + ); + + return Effect.gen(function* () { + const github = yield* CodeHost.Service; + const meta = yield* github.change(1); + expect(Number(meta.number)).toBe(1); + expect(meta.title).toBe("one"); + }).pipe( + Effect.provide(CodeHostGitHub.layer.pipe(Layer.provideMerge(cfg), Layer.provideMerge(proc))), + ); + }); + it.effect("resolves a created GitHub fork PR by its returned URL", () => { const calls: Array> = []; const proc = Layer.succeed(