Skip to content
Draft
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
39 changes: 39 additions & 0 deletions packages/cli/src/commands/review/lib/gh.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import {
setGhHost,
parseNdjson,
gh,
ghApi,
ghApiAll,
ghApiAllNested,
ghRaw,
ghWithInput,
ghWithInputRetried,
Expand Down Expand Up @@ -152,6 +155,42 @@ describe('parseNdjson (the paginated check-runs decode)', () => {
});
});

describe('gh API JSON decoding', () => {
beforeEach(() => {
mockExecFileSync.mockReset();
});

afterEach(() => setGhHost(undefined));

it('parses colorized JSON from ghApi()', () => {
mockExecFileSync.mockReturnValueOnce('\u001b[32m{"body":"ok"}\u001b[39m\n');

expect(ghApi('repos/o/r/issues/1')).toEqual({ body: 'ok' });
});

it('parses colorized array JSON from ghApiAll()', () => {
mockExecFileSync.mockReturnValueOnce(
'\u001b[36m[{"id":1},{"id":2}]\u001b[39m\n',
);

expect(ghApiAll('repos/o/r/pulls/1/comments')).toEqual([
{ id: 1 },
{ id: 2 },
]);
});

it('parses colorized NDJSON from ghApiAllNested()', () => {
mockExecFileSync.mockReturnValueOnce(
'\u001b[33m{"name":"linux"}\u001b[39m\n' +
'\u001b[35m{"name":"mac"}\u001b[39m\n',
);

expect(
ghApiAllNested('repos/o/r/commits/sha/check-runs', 'check_runs'),
).toEqual([{ name: 'linux' }, { name: 'mac' }]);
});
});

// ---------------------------------------------------------------------------
// Transient-error retry
// ---------------------------------------------------------------------------
Expand Down
12 changes: 7 additions & 5 deletions packages/cli/src/commands/review/lib/gh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
// and escaping is consistent across macOS, Linux, and Windows.

import { execFileSync } from 'node:child_process';
import stripAnsi from 'strip-ansi';

// ---------------------------------------------------------------------------
// Transient-error retry
Expand Down Expand Up @@ -288,7 +289,7 @@ export function ghWithInput(input: string, ...args: string[]): string {
export function ghApi(path: string, jq?: string): unknown {
const args = ['api', path];
if (jq) args.push('--jq', jq);
const out = gh(...args);
const out = stripAnsi(gh(...args)).trim();
return out ? JSON.parse(out) : null;
}

Expand All @@ -315,7 +316,7 @@ export function ghApi(path: string, jq?: string): unknown {
* error envelope).
*/
export function ghApiAll(path: string): unknown[] {
const out = gh('api', '--paginate', path);
const out = stripAnsi(gh('api', '--paginate', path)).trim();
if (!out) return [];
const parsed = JSON.parse(out);
return Array.isArray(parsed) ? parsed : [];
Expand Down Expand Up @@ -365,13 +366,14 @@ export function parseNdjson(
if (!out) return [];
const values: unknown[] = [];
for (const line of out.split('\n')) {
if (line.trim().length === 0) continue;
const jsonLine = stripAnsi(line).trim();
if (jsonLine.length === 0) continue;
if (strict) {
values.push(JSON.parse(line));
values.push(JSON.parse(jsonLine));
continue;
}
try {
values.push(JSON.parse(line));
values.push(JSON.parse(jsonLine));
} catch {
// not a JSON record; ignore
}
Expand Down
Loading