@@ -2,6 +2,7 @@ import type { Octokit } from '@octokit/rest'
22
33import { fetchWithRetry } from '@/frame/lib/fetch-utils'
44import warmServer from '@/frame/lib/warm-server'
5+ import { allVersions } from '@/versions/lib/all-versions'
56import github from './github'
67import { 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
2628const PROD_HOST = 'docs.github.com'
2729const 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.
220241async 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 )
0 commit comments