fix: harden Linear integration helper against transient non-JSON errors
This commit is contained in:
parent
a0f16b161e
commit
aa2bda2e32
|
|
@ -48,50 +48,72 @@ function linearGraphQL<T>(query: string, variables: Record<string, unknown>): Pr
|
|||
}
|
||||
const body = JSON.stringify({ query, variables });
|
||||
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
const url = new URL("https://api.linear.app/graphql");
|
||||
const req = request(
|
||||
{
|
||||
hostname: url.hostname,
|
||||
path: url.pathname,
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: LINEAR_API_KEY,
|
||||
"Content-Length": Buffer.byteLength(body),
|
||||
},
|
||||
},
|
||||
(res) => {
|
||||
const chunks: Buffer[] = [];
|
||||
res.on("data", (chunk: Buffer) => chunks.push(chunk));
|
||||
res.on("end", () => {
|
||||
try {
|
||||
const text = Buffer.concat(chunks).toString("utf-8");
|
||||
const json = JSON.parse(text) as {
|
||||
data?: T;
|
||||
errors?: Array<{ message: string }>;
|
||||
};
|
||||
if (json.errors?.length) {
|
||||
reject(new Error(`Linear API error: ${json.errors[0].message}`));
|
||||
return;
|
||||
}
|
||||
resolve(json.data as T);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
async function executeWithRetry(attempt = 1): Promise<T> {
|
||||
try {
|
||||
return await new Promise<T>((resolve, reject) => {
|
||||
const url = new URL("https://api.linear.app/graphql");
|
||||
const req = request(
|
||||
{
|
||||
hostname: url.hostname,
|
||||
path: url.pathname,
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: LINEAR_API_KEY,
|
||||
"Content-Length": Buffer.byteLength(body),
|
||||
},
|
||||
},
|
||||
(res) => {
|
||||
const chunks: Buffer[] = [];
|
||||
res.on("data", (chunk: Buffer) => chunks.push(chunk));
|
||||
res.on("end", () => {
|
||||
const text = Buffer.concat(chunks).toString("utf-8");
|
||||
const statusCode = res.statusCode ?? 0;
|
||||
|
||||
if (statusCode >= 500) {
|
||||
reject(new Error(`Linear API ${statusCode}: ${text.slice(0, 200)}`));
|
||||
return;
|
||||
}
|
||||
|
||||
let json: { data?: T; errors?: Array<{ message: string }> };
|
||||
try {
|
||||
json = JSON.parse(text) as { data?: T; errors?: Array<{ message: string }> };
|
||||
} catch {
|
||||
reject(
|
||||
new Error(`Linear API returned non-JSON (${statusCode}): ${text.slice(0, 200)}`),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (json.errors?.length) {
|
||||
reject(new Error(`Linear API error: ${json.errors[0].message}`));
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(json.data as T);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
req.setTimeout(30_000, () => {
|
||||
req.destroy();
|
||||
reject(new Error("Linear API request timed out"));
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
req.setTimeout(30_000, () => {
|
||||
req.destroy();
|
||||
reject(new Error("Linear API request timed out"));
|
||||
});
|
||||
req.on("error", (err) => reject(err));
|
||||
req.write(body);
|
||||
req.end();
|
||||
});
|
||||
} catch (err) {
|
||||
if (attempt < 3) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1_000));
|
||||
return executeWithRetry(attempt + 1);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
req.on("error", (err) => reject(err));
|
||||
req.write(body);
|
||||
req.end();
|
||||
});
|
||||
return executeWithRetry();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -135,11 +157,13 @@ describe.skipIf(!canRun)("tracker-linear (integration)", () => {
|
|||
|
||||
// Resolve the UUID for cleanup — only possible with direct API key
|
||||
if (LINEAR_API_KEY) {
|
||||
const data = await linearGraphQL<{ issue: { id: string } }>(
|
||||
`query($id: String!) { issue(id: $id) { id } }`,
|
||||
{ id: issueIdentifier },
|
||||
);
|
||||
issueUuid = data.issue.id;
|
||||
try {
|
||||
const data = await linearGraphQL<{ issue: { id: string } }>(
|
||||
`query($id: String!) { issue(id: $id) { id } }`,
|
||||
{ id: issueIdentifier },
|
||||
);
|
||||
issueUuid = data.issue.id;
|
||||
} catch {}
|
||||
}
|
||||
}, 30_000);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue