Skip to content

Commit d3d971d

Browse files
mattinanntclaude
andauthored
fix: use cursor pagination for Dependabot alerts + dry-run dispatch (formbricks#8183)
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent a337930 commit d3d971d

2 files changed

Lines changed: 26 additions & 9 deletions

File tree

.github/workflows/dependabot-to-linear.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ name: Dependabot Alerts to Linear
33
on:
44
schedule:
55
- cron: "0 11 * * *" # 11:00 UTC = 12:00 CET (noon); +1h during CEST
6-
workflow_dispatch: {}
6+
workflow_dispatch:
7+
inputs:
8+
dry_run:
9+
description: "Log what would be created without writing to Linear"
10+
type: boolean
11+
default: false
712

813
permissions:
914
contents: read
@@ -40,4 +45,4 @@ jobs:
4045
# Fallback used by the script if LINEAR_API_KEY is not set; must be
4146
# listed here because the job only sees secrets exposed via env.
4247
LINEAR_ACCESS_KEY: ${{ secrets.LINEAR_ACCESS_KEY }}
43-
run: node scripts/dependabot-to-linear.mjs
48+
run: node scripts/dependabot-to-linear.mjs ${{ inputs.dry_run && '--dry-run' || '' }}

scripts/dependabot-to-linear.mjs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212
// GITHUB_REPOSITORY "owner/repo" (provided automatically in Actions)
1313
// DEPENDABOT_ALERTS_TOKEN token with "Dependabot alerts: read" (the default
1414
// GITHUB_TOKEN cannot read this API)
15-
// LINEAR_API_KEY | LINEAR_ACCESS_KEY Linear key with issue-create access
15+
// LINEAR_API_KEY | LINEAR_ACCESS_KEY Linear personal API key with BOTH
16+
// `read` (to dedupe against existing issues) and
17+
// `write` / `issues:create` (to create new ones).
18+
// Generated at Settings → API → Personal API keys.
1619
// Flags:
1720
// --dry-run log what would be created without writing to Linear
1821

@@ -47,12 +50,22 @@ function requireEnv(name, ...fallbacks) {
4750
throw new Error(`Missing required env var: ${[name, ...fallbacks].join(" or ")}`);
4851
}
4952

53+
// The Dependabot alerts endpoint uses cursor pagination (`before`/`after`),
54+
// not `?page=`. The next-page cursor is returned in the response `Link` header
55+
// as `<url>; rel="next"`. Walk that until there's no next link.
56+
function parseNextLink(linkHeader) {
57+
if (!linkHeader) return null;
58+
for (const part of linkHeader.split(",")) {
59+
const match = part.match(/<([^>]+)>\s*;\s*rel="next"/);
60+
if (match) return match[1];
61+
}
62+
return null;
63+
}
64+
5065
async function fetchOpenAlerts(repo, token) {
5166
const alerts = [];
52-
let page = 1;
53-
const perPage = 100;
54-
for (;;) {
55-
const url = `https://api.github.com/repos/${repo}/dependabot/alerts?state=open&per_page=${perPage}&page=${page}`;
67+
let url = `https://api.github.com/repos/${repo}/dependabot/alerts?state=open&per_page=100`;
68+
while (url) {
5669
const res = await fetch(url, {
5770
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
5871
headers: {
@@ -68,8 +81,7 @@ async function fetchOpenAlerts(repo, token) {
6881
}
6982
const batch = await res.json();
7083
alerts.push(...batch);
71-
if (batch.length < perPage) break;
72-
page += 1;
84+
url = parseNextLink(res.headers.get("link"));
7385
}
7486
return alerts;
7587
}

0 commit comments

Comments
 (0)