Skip to content

Commit 3379a99

Browse files
heiskrCopilot
andauthored
Limit auto hard-purge to latest version of each plan (#62313)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9e11d034-61da-499c-bbc3-0722e31d9b5f
1 parent a81b3ba commit 3379a99

2 files changed

Lines changed: 48 additions & 5 deletions

File tree

src/workflows/purge-fastly-changed-content.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Octokit } from '@octokit/rest'
22

33
import { fetchWithRetry } from '@/frame/lib/fetch-utils'
44
import warmServer from '@/frame/lib/warm-server'
5+
import { allVersions } from '@/versions/lib/all-versions'
56
import github from './github'
67
import { getActionContext } from './action-context'
78

@@ -19,13 +20,31 @@ import { getActionContext } from './action-context'
1920
// fresh. By the time deployment succeeds, the old pods are already terminated
2021
// (Heaven waits for the rollout to fully reconcile), so this is deterministic.
2122
//
22-
// Scope: content/ only. data/ changes (reusables, variables, ...) fan out to
23-
// far too many URLs to enumerate cheaply, so they stay covered by the soft
24-
// purge of the whole language.
23+
// Scope: content/ only, and only the latest release of each plan (fpt, ghec,
24+
// latest ghes — see PURGEABLE_PAGE_VERSIONS). data/ changes (reusables,
25+
// variables, ...) fan out to far too many URLs to enumerate cheaply, so they
26+
// stay covered by the soft purge of the whole language.
2527

2628
const PROD_HOST = 'docs.github.com'
2729
const CONTENT_PREFIX = 'content/'
2830

31+
// Which page versions we hard-purge. A page that applies to every
32+
// version fans out to one URL per version: fpt + ghec + one per *supported* GHES
33+
// release (8+ URLs today, growing with each GHES release). Enumerating all of
34+
// them for a large deploy blew past Fastly's URL-purge rate limit (HTTP 429) and
35+
// failed the job. So we only target the newest ("latest") release of each plan:
36+
// free-pro-team (fpt), enterprise-cloud (ghec), and the newest supported
37+
// enterprise-server (ghes). Older still-supported GHES releases change far less
38+
// often and remain covered by the routine soft purge of the whole `language:en`
39+
// key that always runs. A version is the latest of its plan exactly when its
40+
// `version` equals its plan's `latestVersion`, so this set is {fpt, ghec, latest
41+
// ghes} and updates itself as GHES releases come and go.
42+
export const PURGEABLE_PAGE_VERSIONS = new Set(
43+
Object.values(allVersions)
44+
.filter((version) => version.version === version.latestVersion)
45+
.map((version) => version.version),
46+
)
47+
2948
// A defensive ceiling. A normal content deploy touches a handful of files ->
3049
// tens of URLs. If a deploy changes so much that we'd hard-purge more than this,
3150
// skip the targeted purge: the soft purge of the whole `language:en` key (which
@@ -216,14 +235,19 @@ export function contentFilesToEnglishUrls(
216235
return [...urls]
217236
}
218237

219-
// Build relativePath -> English permalink hrefs from the warmed page list.
238+
// Build relativePath -> English permalink hrefs from the warmed page list. Only
239+
// the latest release of each plan is kept (see PURGEABLE_PAGE_VERSIONS) to cap
240+
// the URL fan-out that was tripping Fastly's purge rate limiter.
220241
async function loadEnglishPermalinkIndex(): Promise<Map<string, string[]>> {
221242
const { pageList } = await warmServer(['en'])
222243
const index = new Map<string, string[]>()
223244
for (const page of pageList) {
224245
if (page.languageCode !== 'en') continue
225246
const hrefs = page.permalinks
226-
.filter((permalink) => permalink.languageCode === 'en')
247+
.filter(
248+
(permalink) =>
249+
permalink.languageCode === 'en' && PURGEABLE_PAGE_VERSIONS.has(permalink.pageVersion),
250+
)
227251
.map((permalink) => permalink.href)
228252
if (hrefs.length) {
229253
index.set(page.relativePath, hrefs)

src/workflows/tests/purge-fastly-changed-content.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@ const {
1515
contentFilesToEnglishUrls,
1616
hardPurgeUrls,
1717
rateLimitDelayMs,
18+
PURGEABLE_PAGE_VERSIONS,
1819
} = await import('../purge-fastly-changed-content')
1920

21+
const { latest: latestGhes } = await import('@/versions/lib/enterprise-server-releases')
22+
2023
afterEach(() => {
2124
vi.clearAllMocks()
2225
})
@@ -150,6 +153,22 @@ describe('contentFilesToEnglishUrls', () => {
150153
})
151154
})
152155

156+
describe('PURGEABLE_PAGE_VERSIONS', () => {
157+
test('is only the latest release of each plan (fpt, ghec, latest ghes)', () => {
158+
expect([...PURGEABLE_PAGE_VERSIONS].sort()).toEqual(
159+
['free-pro-team@latest', 'enterprise-cloud@latest', `enterprise-server@${latestGhes}`].sort(),
160+
)
161+
})
162+
163+
test('excludes older, still-supported GHES releases', () => {
164+
for (const version of PURGEABLE_PAGE_VERSIONS) {
165+
if (version.startsWith('enterprise-server@')) {
166+
expect(version).toBe(`enterprise-server@${latestGhes}`)
167+
}
168+
}
169+
})
170+
})
171+
153172
describe('hardPurgeUrls', () => {
154173
// A minimal stand-in for a fetch Response, with a case-insensitive headers.get.
155174
function fakeResponse(

0 commit comments

Comments
 (0)