@@ -19,6 +19,8 @@ import {
1919 checkAssetLink ,
2020 isAssetLink ,
2121 getRelativePath ,
22+ getLinkCheckVersions ,
23+ type ExtractedLink ,
2224} from '@/links/lib/extract-links'
2325import {
2426 buildRenderContext ,
@@ -33,6 +35,7 @@ import {
3335 type CrossPageAnchorFlaw ,
3436 generatePRComment ,
3537 groupBrokenLinks ,
38+ mergeVersionedLinkFlaws ,
3639} from '@/links/lib/link-report'
3740import { uploadArtifact } from '@/links/scripts/upload-artifact'
3841import github from '@/workflows/github'
@@ -50,67 +53,72 @@ interface CheckResult {
5053
5154async function checkFile (
5255 filePath : string ,
56+ sourcePage : Page | undefined ,
5357 pageMap : Record < string , Page > ,
5458 redirects : Record < string , string > ,
55- version : string = 'free-pro-team@latest' ,
5659 language : string = 'en' ,
5760) : Promise < CheckResult > {
58- const brokenLinks : BrokenLink [ ] = [ ]
59- const redirectLinks : BrokenLink [ ] = [ ]
6061 let totalLinksChecked = 0
6162
6263 let content : string
6364 try {
6465 content = fs . readFileSync ( filePath , 'utf-8' )
6566 } catch {
6667 console . warn ( `Could not read file: ${ filePath } ` )
67- return { file : filePath , brokenLinks, redirectLinks, totalLinksChecked }
68+ return { file : filePath , brokenLinks : [ ] , redirectLinks : [ ] , totalLinksChecked }
6869 }
6970
70- const context = createLiquidContext ( version , language )
71+ const file = getRelativePath ( filePath )
72+ const flaws : BrokenLink [ ] = [ ]
73+ // Liquid shifts rendered lines, so use raw source lines unless a Liquid-built href has none.
74+ const flawFor = ( link : ExtractedLink ) : BrokenLink => {
75+ const lines = findLinkLines (
76+ content ,
77+ link . fragment ? `${ link . href } #${ link . fragment } ` : link . href ,
78+ )
79+ return {
80+ href : link . href ,
81+ file,
82+ lines : lines . length ? lines : [ link . line ] ,
83+ text : link . text ,
84+ isAutotitle : link . isAutotitle ,
85+ }
86+ }
7187
72- const { internalLinks } = await extractLinksWithLiquid ( content , context )
88+ // ifversion blocks and versionless links depend on the version, so check each one.
89+ for ( const version of getLinkCheckVersions ( sourcePage ) ) {
90+ // Pages use the anchor check's context so version names and feature flags evaluate like the site.
91+ const context = sourcePage
92+ ? buildRenderContext ( sourcePage , version , pageMap , redirects )
93+ : createLiquidContext ( version , language )
7394
74- // imageLinks are excluded: they're static assets, not docs pages.
75- totalLinksChecked = internalLinks . length
95+ const { internalLinks } = await extractLinksWithLiquid ( content , context )
7696
77- for ( const link of internalLinks ) {
78- if ( isAssetLink ( link . href ) ) {
79- if ( ! checkAssetLink ( link . href ) ) {
80- brokenLinks . push ( {
81- href : link . href ,
82- file : getRelativePath ( filePath ) ,
83- lines : [ link . line ] ,
84- text : link . text ,
85- isAutotitle : link . isAutotitle ,
86- } )
97+ // imageLinks are excluded: they're static assets, not docs pages.
98+ totalLinksChecked = Math . max ( totalLinksChecked , internalLinks . length )
99+
100+ for ( const link of internalLinks ) {
101+ if ( isAssetLink ( link . href ) ) {
102+ if ( ! checkAssetLink ( link . href ) ) flaws . push ( flawFor ( link ) )
103+ continue
87104 }
88- continue
89- }
90105
91- const result = checkInternalLink ( link . href , pageMap , redirects )
92-
93- if ( ! result . exists ) {
94- brokenLinks . push ( {
95- href : link . href ,
96- file : getRelativePath ( filePath ) ,
97- lines : [ link . line ] ,
98- text : link . text ,
99- isAutotitle : link . isAutotitle ,
100- } )
101- } else if ( result . isRedirect ) {
102- redirectLinks . push ( {
103- href : link . href ,
104- file : getRelativePath ( filePath ) ,
105- lines : [ link . line ] ,
106- text : link . text ,
107- isAutotitle : link . isAutotitle ,
108- isRedirect : true ,
109- redirectTarget : result . redirectTarget ,
110- } )
106+ const result = checkInternalLink ( link . href , pageMap , redirects , version , language )
107+
108+ if ( ! result . exists ) {
109+ flaws . push ( flawFor ( link ) )
110+ } else if ( result . isRedirect ) {
111+ flaws . push ( {
112+ ...flawFor ( link ) ,
113+ isRedirect : true ,
114+ redirectTarget : result . redirectTarget ,
115+ requiresVersionContext : result . requiresVersionContext ,
116+ } )
117+ }
111118 }
112119 }
113120
121+ const { brokenLinks, redirectLinks } = mergeVersionedLinkFlaws ( flaws )
114122 return { file : filePath , brokenLinks, redirectLinks, totalLinksChecked }
115123}
116124
@@ -369,13 +377,13 @@ async function main() {
369377 console . log ( ` Checking: ${ file } ` )
370378 }
371379
372- const result = await checkFile ( filePath , pageMap , redirects )
380+ const sourcePage = pageByRelativePath . get ( getRelativePath ( filePath ) )
381+ const result = await checkFile ( filePath , sourcePage , pageMap , redirects )
373382 allBrokenLinks . push ( ...result . brokenLinks )
374383 allRedirectLinks . push ( ...result . redirectLinks )
375384 totalLinksChecked += result . totalLinksChecked
376385
377386 // Skip changed files outside pageByRelativePath because data and reusables have no versions of their own.
378- const sourcePage = pageByRelativePath . get ( getRelativePath ( filePath ) )
379387 if ( checkAnchors && sourcePage ) {
380388 allBrokenAnchors . push (
381389 ...( await checkFileAnchors ( filePath , sourcePage , pageMap , redirects , headingCache ) ) ,
0 commit comments