Skip to content

Commit c25240f

Browse files
authored
Merge pull request #45331 from github/repo-sync
Repo sync
2 parents 8ecd09e + 7e754b4 commit c25240f

31 files changed

Lines changed: 2295 additions & 344 deletions

.github/workflows/link-check-external.yml

Lines changed: 158 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,22 @@ jobs:
1818
if: github.repository == 'github/docs-internal'
1919
runs-on: ubuntu-latest
2020
timeout-minutes: 180 # 3 hours for external checks
21+
# Serialize publishing so two overlapping runs can't both create a
22+
# "rolling" issue, or write their results out of order.
23+
concurrency:
24+
group: broken-external-links-report
25+
cancel-in-progress: false
2126
steps:
2227
- name: Checkout
2328
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
29+
2430
- uses: ./.github/actions/node-npm-setup
2531

2632
- name: Install dependencies
2733
run: npm ci
2834

2935
- name: Check external links
36+
id: check
3037
env:
3138
ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
3239
CACHE_MAX_AGE_DAYS: '7'
@@ -57,15 +64,158 @@ jobs:
5764
echo "No broken link report generated - all links valid!"
5865
fi
5966
60-
- name: Create issue if broken links found
61-
if: always() && steps.check_report.outputs.has_report == 'true'
62-
uses: peter-evans/create-issue-from-file@65115121ba9a3573cbaded4dc66b90ba1f9b69dc
67+
- name: Create or update the rolling report issue
68+
if: |
69+
always()
70+
&& steps.check.outcome == 'success'
71+
&& steps.check_report.outputs.has_report == 'true'
72+
&& !inputs.max_urls
73+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3
6374
with:
64-
token: ${{ secrets.DOCS_BOT_PAT_BASE }}
65-
repository: github/docs-content
66-
title: '🌐 Broken External Links Report'
67-
content-filepath: artifacts/external-link-report.md
68-
labels: broken link report
75+
github-token: ${{ secrets.DOCS_BOT_PAT_BASE }}
76+
script: |
77+
const fs = require('fs')
78+
const title = '🌐 Broken External Links Report'
79+
const owner = 'github'
80+
const repo = 'docs-content'
81+
const label = 'broken link report'
82+
83+
// GitHub rejects issue bodies over 65536 characters with a 422.
84+
// Truncate and point at the run artifact for the full contents.
85+
const MAX_BODY_SIZE = 60000
86+
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`
87+
let body = fs.readFileSync('artifacts/external-link-report.md', 'utf8')
88+
if (body.length > MAX_BODY_SIZE) {
89+
const notice = `\n\n---\n\n*Report truncated. Download the full report from the [workflow run artifacts](${runUrl}).*`
90+
body = body.slice(0, MAX_BODY_SIZE - notice.length) + notice
91+
core.warning(`Report exceeded ${MAX_BODY_SIZE} characters, so it was truncated.`)
92+
}
93+
94+
// Reuse a single rolling issue instead of opening a new one every
95+
// week, which floods the first responders' board. Find the open
96+
// report issues (newest first).
97+
const open = await github.paginate(github.rest.issues.listForRepo, {
98+
owner,
99+
repo,
100+
state: 'open',
101+
labels: label,
102+
per_page: 100,
103+
})
104+
const reportIssues = open
105+
.filter((issue) => !issue.pull_request && issue.title === title)
106+
.sort((a, b) => b.number - a.number)
107+
108+
if (reportIssues.length === 0) {
109+
const created = await github.rest.issues.create({
110+
owner,
111+
repo,
112+
title,
113+
body,
114+
labels: [label],
115+
})
116+
core.info(`Created rolling report issue: ${created.data.html_url}`)
117+
return
118+
}
119+
120+
// Refresh the newest open report in place and close any older
121+
// duplicates so exactly one canonical issue remains.
122+
const [canonical, ...superseded] = reportIssues
123+
await github.rest.issues.update({
124+
owner,
125+
repo,
126+
issue_number: canonical.number,
127+
title,
128+
body,
129+
})
130+
core.info(`Updated rolling report issue: ${canonical.html_url}`)
131+
132+
// Attempt every duplicate even if one fails, so a single transient
133+
// API error doesn't leave the rest open.
134+
const results = await Promise.allSettled(
135+
superseded.map(async (issue) => {
136+
await github.rest.issues.createComment({
137+
owner,
138+
repo,
139+
issue_number: issue.number,
140+
body: `Superseded by the current rolling report: #${canonical.number}.`,
141+
})
142+
await github.rest.issues.update({
143+
owner,
144+
repo,
145+
issue_number: issue.number,
146+
state: 'closed',
147+
state_reason: 'not_planned',
148+
})
149+
core.info(`Closed superseded report issue #${issue.number}`)
150+
}),
151+
)
152+
const failures = results.filter((result) => result.status === 'rejected')
153+
if (failures.length > 0) {
154+
throw new AggregateError(
155+
failures.map((failure) => failure.reason),
156+
`Failed to close ${failures.length} superseded report issue(s).`,
157+
)
158+
}
159+
160+
- name: Close the rolling report issue when all links are valid
161+
if: |
162+
always()
163+
&& steps.check.outcome == 'success'
164+
&& steps.check_report.outputs.has_report == 'false'
165+
&& !inputs.max_urls
166+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3
167+
with:
168+
github-token: ${{ secrets.DOCS_BOT_PAT_BASE }}
169+
script: |
170+
const title = '🌐 Broken External Links Report'
171+
const owner = 'github'
172+
const repo = 'docs-content'
173+
const label = 'broken link report'
174+
175+
// A clean run means the open report is stale. Leaving it open would
176+
// keep fixed failures on the first responders' board.
177+
const open = await github.paginate(github.rest.issues.listForRepo, {
178+
owner,
179+
repo,
180+
state: 'open',
181+
labels: label,
182+
per_page: 100,
183+
})
184+
const reportIssues = open.filter(
185+
(issue) => !issue.pull_request && issue.title === title,
186+
)
187+
188+
if (reportIssues.length === 0) {
189+
core.info('No open report issue to close.')
190+
return
191+
}
192+
193+
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`
194+
const results = await Promise.allSettled(
195+
reportIssues.map(async (issue) => {
196+
await github.rest.issues.createComment({
197+
owner,
198+
repo,
199+
issue_number: issue.number,
200+
body: `All external links are valid as of the [latest run](${runUrl}). Closing this report. A new one opens if links break again.`,
201+
})
202+
await github.rest.issues.update({
203+
owner,
204+
repo,
205+
issue_number: issue.number,
206+
state: 'closed',
207+
state_reason: 'completed',
208+
})
209+
core.info(`Closed resolved report issue #${issue.number}`)
210+
}),
211+
)
212+
const failures = results.filter((result) => result.status === 'rejected')
213+
if (failures.length > 0) {
214+
throw new AggregateError(
215+
failures.map((failure) => failure.reason),
216+
`Failed to close ${failures.length} resolved report issue(s).`,
217+
)
218+
}
69219
70220
- uses: ./.github/actions/create-workflow-failure-issue
71221
id: create-failure-issue

.github/workflows/link-check-internal.yml

Lines changed: 157 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ on:
2020
required: false
2121
default: false
2222
create_report:
23-
description: 'Create the combined broken links report issue in docs-content'
23+
description: "Publish the combined report to the rolling docs-content issue. A manual run only covers one version/language, so it will overwrite the scheduled run's fuller report."
2424
type: boolean
2525
required: false
26-
default: true
26+
default: false
2727

2828
permissions:
2929
contents: read
@@ -202,6 +202,11 @@ jobs:
202202
if: always() && github.repository == 'github/docs-internal'
203203
needs: [setup-matrix, check-internal-links]
204204
runs-on: ubuntu-latest
205+
# Serialize publishing so two overlapping runs can't both create a
206+
# "rolling" issue, or write their results out of order.
207+
concurrency:
208+
group: broken-internal-links-report
209+
cancel-in-progress: false
205210
permissions:
206211
contents: read
207212
issues: write
@@ -239,15 +244,157 @@ jobs:
239244
echo "No broken link reports generated - all links valid!"
240245
fi
241246
242-
- name: Create issue if broken links found
243-
if: steps.combine.outputs.has_reports == 'true' && (github.event_name != 'workflow_dispatch' || inputs.create_report != false)
244-
uses: peter-evans/create-issue-from-file@fca9117c27cdc29c6c4db3b86c48e4115a786710 # v5
247+
- name: Create or update the rolling report issue
248+
if: |
249+
steps.combine.outputs.has_reports == 'true'
250+
&& needs.check-internal-links.result == 'success'
251+
&& (github.event_name != 'workflow_dispatch' || inputs.create_report)
252+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3
245253
with:
246-
token: ${{ secrets.DOCS_BOT_PAT_BASE }}
247-
repository: github/docs-content
248-
title: '🔗 Broken Internal Links Report'
249-
content-filepath: combined-report.md
250-
labels: broken link report
254+
github-token: ${{ secrets.DOCS_BOT_PAT_BASE }}
255+
script: |
256+
const fs = require('fs')
257+
const title = '🔗 Broken Internal Links Report'
258+
const owner = 'github'
259+
const repo = 'docs-content'
260+
const label = 'broken link report'
261+
262+
// GitHub rejects issue bodies over 65536 characters with a 422. The
263+
// internal report routinely exceeds that, so truncate and point at
264+
// the run artifact for the full contents.
265+
const MAX_BODY_SIZE = 60000
266+
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`
267+
let body = fs.readFileSync('combined-report.md', 'utf8')
268+
if (body.length > MAX_BODY_SIZE) {
269+
const notice = `\n\n---\n\n*Report truncated. Download the full report from the [workflow run artifacts](${runUrl}).*`
270+
body = body.slice(0, MAX_BODY_SIZE - notice.length) + notice
271+
core.warning(`Report exceeded ${MAX_BODY_SIZE} characters, so it was truncated.`)
272+
}
273+
274+
// Reuse a single rolling issue instead of opening a new one every
275+
// week, which floods the first responders' board. Find the open
276+
// report issues (newest first).
277+
const open = await github.paginate(github.rest.issues.listForRepo, {
278+
owner,
279+
repo,
280+
state: 'open',
281+
labels: label,
282+
per_page: 100,
283+
})
284+
const reportIssues = open
285+
.filter((issue) => !issue.pull_request && issue.title === title)
286+
.sort((a, b) => b.number - a.number)
287+
288+
if (reportIssues.length === 0) {
289+
const created = await github.rest.issues.create({
290+
owner,
291+
repo,
292+
title,
293+
body,
294+
labels: [label],
295+
})
296+
core.info(`Created rolling report issue: ${created.data.html_url}`)
297+
return
298+
}
299+
300+
// Refresh the newest open report in place and close any older
301+
// duplicates so exactly one canonical issue remains.
302+
const [canonical, ...superseded] = reportIssues
303+
await github.rest.issues.update({
304+
owner,
305+
repo,
306+
issue_number: canonical.number,
307+
title,
308+
body,
309+
})
310+
core.info(`Updated rolling report issue: ${canonical.html_url}`)
311+
312+
// Attempt every duplicate even if one fails, so a single transient
313+
// API error doesn't leave the rest open.
314+
const results = await Promise.allSettled(
315+
superseded.map(async (issue) => {
316+
await github.rest.issues.createComment({
317+
owner,
318+
repo,
319+
issue_number: issue.number,
320+
body: `Superseded by the current rolling report: #${canonical.number}.`,
321+
})
322+
await github.rest.issues.update({
323+
owner,
324+
repo,
325+
issue_number: issue.number,
326+
state: 'closed',
327+
state_reason: 'not_planned',
328+
})
329+
core.info(`Closed superseded report issue #${issue.number}`)
330+
}),
331+
)
332+
const failures = results.filter((result) => result.status === 'rejected')
333+
if (failures.length > 0) {
334+
throw new AggregateError(
335+
failures.map((failure) => failure.reason),
336+
`Failed to close ${failures.length} superseded report issue(s).`,
337+
)
338+
}
339+
340+
- name: Close the rolling report issue when all links are valid
341+
if: |
342+
steps.combine.outputs.has_reports == 'false'
343+
&& needs.check-internal-links.result == 'success'
344+
&& github.event_name != 'workflow_dispatch'
345+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3
346+
with:
347+
github-token: ${{ secrets.DOCS_BOT_PAT_BASE }}
348+
script: |
349+
const title = '🔗 Broken Internal Links Report'
350+
const owner = 'github'
351+
const repo = 'docs-content'
352+
const label = 'broken link report'
353+
354+
// A clean run means the open report is stale. Leaving it open would
355+
// keep fixed failures on the first responders' board.
356+
const open = await github.paginate(github.rest.issues.listForRepo, {
357+
owner,
358+
repo,
359+
state: 'open',
360+
labels: label,
361+
per_page: 100,
362+
})
363+
const reportIssues = open.filter(
364+
(issue) => !issue.pull_request && issue.title === title,
365+
)
366+
367+
if (reportIssues.length === 0) {
368+
core.info('No open report issue to close.')
369+
return
370+
}
371+
372+
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`
373+
const results = await Promise.allSettled(
374+
reportIssues.map(async (issue) => {
375+
await github.rest.issues.createComment({
376+
owner,
377+
repo,
378+
issue_number: issue.number,
379+
body: `All internal links are valid as of the [latest run](${runUrl}). Closing this report. A new one opens if links break again.`,
380+
})
381+
await github.rest.issues.update({
382+
owner,
383+
repo,
384+
issue_number: issue.number,
385+
state: 'closed',
386+
state_reason: 'completed',
387+
})
388+
core.info(`Closed resolved report issue #${issue.number}`)
389+
}),
390+
)
391+
const failures = results.filter((result) => result.status === 'rejected')
392+
if (failures.length > 0) {
393+
throw new AggregateError(
394+
failures.map((failure) => failure.reason),
395+
`Failed to close ${failures.length} resolved report issue(s).`,
396+
)
397+
}
251398
252399
- uses: ./.github/actions/create-workflow-failure-issue
253400
id: create-failure-issue

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
# ---------------------------------------------------------------
1111
# To update the sha:
1212
# https://github.com/github/gh-base-image/pkgs/container/gh-base-image%2Fgh-base-noble
13-
FROM ghcr.io/github/gh-base-image/gh-base-noble:20260722-151519-g000ce495e@sha256:f722b1fb6d02a18f85d45ab2e064a0736e64992f20371a063c4076bac57832c9 AS base
13+
FROM ghcr.io/github/gh-base-image/gh-base-noble:20260727-152635-gd2e4a1fa6@sha256:22e3a406a3f0f9bf6d3544ce4189bb947e467330bbe7210af5ecc727232a569c AS base
1414

1515
# Install curl for Node install and determining the early access branch
1616
# Install git for cloning docs-early-access & translations repos

0 commit comments

Comments
 (0)