Skip to content

Commit d498aa2

Browse files
committed
feat: report GraphQL error details
Preserve all GraphQL errors and render their structured data as JSON so paths, locations, extensions, and additional errors remain visible in command logs. Assisted-by: codex:gpt-5.6-sol Signed-off-by: Filip Skokan <panva.ip@gmail.com>
1 parent 812f207 commit d498aa2

5 files changed

Lines changed: 117 additions & 6 deletions

File tree

components/git/metadata.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { parsePRFromURL } from '../../lib/links.js';
22
import { getMetadata } from '../metadata.js';
33
import CLI from '../../lib/cli.js';
44
import { getMergedConfig } from '../../lib/config.js';
5-
import { runPromise, IGNORE } from '../../lib/run.js';
5+
import { runPromise, reportError, IGNORE } from '../../lib/run.js';
66

77
export const command = 'metadata <identifier>';
88
export const describe =
@@ -67,7 +67,7 @@ export async function writeMetadataJsonResult(metadataPromise) {
6767
await writeStdout(`${JSON.stringify(json, null, 2)}\n`);
6868
process.exitCode = json.exitCode;
6969
} catch (error) {
70-
console.error(error);
70+
reportError(error);
7171
process.exitCode = 1;
7272
}
7373
}

lib/request.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -335,11 +335,12 @@ export default class Request {
335335
};
336336

337337
const result = await this.json(url, options);
338-
if (result.errors) {
338+
if (result.errors?.length > 0) {
339339
const { type, message } = result.errors[0];
340340
const err = new Error(`[${type}] GraphQL request Error: ${message}`);
341341
err.data = {
342-
variables
342+
variables,
343+
errors: result.errors
343344
};
344345
throw err;
345346
}

lib/run.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ import {
88

99
export const IGNORE = '__ignore__';
1010

11+
export function reportError(error, write = console.error) {
12+
write(error?.stack ?? error);
13+
if (error?.data !== undefined) {
14+
write(JSON.stringify(error.data, null, 2));
15+
}
16+
}
17+
1118
function runAsyncBase(cmd, args, {
1219
ignoreFailure = true,
1320
spawnArgs,
@@ -72,7 +79,7 @@ export function forceRunAsync(cmd, args, options) {
7279
return runAsyncBase(cmd, args, options).catch((error) => {
7380
if (error.message !== IGNORE) {
7481
if (!error.messageOnly) {
75-
console.error(error);
82+
reportError(error);
7683
}
7784
throw error;
7885
}
@@ -82,7 +89,7 @@ export function forceRunAsync(cmd, args, options) {
8289
export function runPromise(promise) {
8390
return promise.catch((error) => {
8491
if (error.message !== IGNORE) {
85-
console.error(error);
92+
reportError(error);
8693
}
8794
exit();
8895
});

test/unit/request.test.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { describe, it } from 'node:test';
2+
import assert from 'node:assert';
3+
4+
import Request from '../../lib/request.js';
5+
6+
function createRequest(response) {
7+
const request = Object.create(Request.prototype);
8+
request.credentials = { github: 'credentials' };
9+
request.proxyAgent = undefined;
10+
request.json = async() => response;
11+
return request;
12+
}
13+
14+
describe('Request', () => {
15+
describe('query', () => {
16+
it('preserves detailed GraphQL errors', async() => {
17+
const variables = { owner: 'nodejs', repo: 'node', prid: 65130 };
18+
const errors = [
19+
{
20+
type: 'FORBIDDEN',
21+
path: [
22+
'repository', 'pullRequest', 'commits', 'nodes', 0, 'commit',
23+
'checkSuites', 'edges', 4, 'node', 'app'
24+
],
25+
extensions: { saml_failure: false },
26+
locations: [{ line: 26, column: 7 }],
27+
message: 'Resource not accessible by integration'
28+
},
29+
{
30+
type: 'FORBIDDEN',
31+
path: ['repository', 'pullRequest', 'files'],
32+
locations: [{ line: 42, column: 5 }],
33+
message: 'A second error'
34+
}
35+
];
36+
const request = createRequest({ errors });
37+
38+
await assert.rejects(
39+
request.query('query PR { pullRequest { id } }', variables),
40+
(error) => {
41+
assert.strictEqual(
42+
error.message,
43+
'[FORBIDDEN] GraphQL request Error: ' +
44+
'Resource not accessible by integration');
45+
assert.deepStrictEqual(error.data, { variables, errors });
46+
return true;
47+
});
48+
});
49+
50+
it('preserves top-level GraphQL API errors', async() => {
51+
const variables = { owner: 'nodejs', repo: 'node', prid: 65130 };
52+
const request = createRequest({ message: 'Bad credentials' });
53+
54+
await assert.rejects(
55+
request.query('query PR { pullRequest { id } }', variables),
56+
(error) => {
57+
assert.strictEqual(
58+
error.message,
59+
'GraphQL request Error: Bad credentials');
60+
assert.deepStrictEqual(error.data, { variables });
61+
return true;
62+
});
63+
});
64+
});
65+
});

test/unit/run.test.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { describe, it } from 'node:test';
2+
import assert from 'node:assert';
3+
4+
import { reportError } from '../../lib/run.js';
5+
6+
describe('reportError', () => {
7+
it('renders structured error data as formatted JSON', () => {
8+
const error = new Error(
9+
'[FORBIDDEN] GraphQL request Error: Resource not accessible');
10+
error.data = {
11+
variables: { owner: 'nodejs', repo: 'node', prid: 65130 },
12+
errors: [{
13+
type: 'FORBIDDEN',
14+
path: ['repository', 'pullRequest', 'checkSuites', 0, 'app'],
15+
locations: [{ line: 26, column: 7 }],
16+
extensions: { saml_failure: false },
17+
message: 'Resource not accessible'
18+
}]
19+
};
20+
const output = [];
21+
22+
reportError(error, (value) => output.push(value));
23+
24+
assert.deepStrictEqual(output, [
25+
error.stack,
26+
JSON.stringify(error.data, null, 2)
27+
]);
28+
});
29+
30+
it('renders errors without structured data once', () => {
31+
const error = new Error('boom');
32+
const output = [];
33+
34+
reportError(error, (value) => output.push(value));
35+
36+
assert.deepStrictEqual(output, [error.stack]);
37+
});
38+
});

0 commit comments

Comments
 (0)