@@ -28,6 +28,8 @@ import fs from 'fs'
2828import path from 'path'
2929import { RAIL_SLUGS } from '../src/data/seo/deposit-rails'
3030import { SUPPORTED_LOCALES } from '../src/i18n/types'
31+ import { isPublishedContent , parseContentFrontmatter } from './verify-content-frontmatter'
32+ import { isKnownRouteOrLocaleRedirect } from './verify-content-routes'
3133
3234const ROOT = path . join ( process . cwd ( ) , 'src/content' )
3335const CONTENT_DIR = path . join ( ROOT , 'content' )
@@ -37,15 +39,6 @@ const PRIMARY_LOCALES = ['en', 'es-419', 'pt-br']
3739const LOCALE_PATH_PREFIX = new RegExp (
3840 `^/(${ SUPPORTED_LOCALES . map ( ( locale ) => locale . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) ) . join ( '|' ) } )/`
3941)
40- // These locales no longer render pages, but their old URLs remain valid link
41- // destinations because redirects.json forwards both the bare root and every
42- // subpath. Keep this list separate from SUPPORTED_LOCALES: adding it to the
43- // route/sitemap loops would recreate phantom static pages.
44- const REDIRECTED_LOCALE_ALIASES = [ 'es-es' ]
45- const REDIRECTED_LOCALE_PATH_PREFIX = new RegExp (
46- `^/(${ REDIRECTED_LOCALE_ALIASES . map ( ( locale ) => locale . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) ) . join ( '|' ) } )(?:/|$)`
47- )
48-
4942// `content/deposit/` mixes two URL families on the same dynamic route:
5043// exchanges → /{locale}/deposit/from-{slug}
5144// rails → /{locale}/deposit/via-{slug}
@@ -96,7 +89,7 @@ function gateReceiveSources(): string[] {
9689 . filter ( ( slug ) => slug !== 'index' )
9790 . filter ( ( slug ) => {
9891 const en = path . join ( CONTENT_DIR , 'receive-from' , slug , 'en.md' )
99- return fs . existsSync ( en ) && isPublished ( fs . readFileSync ( en , 'utf-8' ) )
92+ return fs . existsSync ( en ) && isPublishedContent ( fs . readFileSync ( en , 'utf-8' ) )
10093 } )
10194}
10295
@@ -132,36 +125,6 @@ function isContentPage(filePath: string): boolean {
132125 return true
133126}
134127
135- // --- Frontmatter parsing ---
136-
137- function parseFrontmatter ( content : string ) : Record < string , unknown > {
138- const match = content . match ( / ^ - - - \n ( [ \s \S ] * ?) \n - - - / )
139- if ( ! match ) return { }
140- const frontmatter : Record < string , unknown > = { }
141- for ( const line of match [ 1 ] . split ( '\n' ) ) {
142- const colonIdx = line . indexOf ( ':' )
143- if ( colonIdx === - 1 ) continue
144- const key = line . slice ( 0 , colonIdx ) . trim ( )
145- let value : string | boolean = line . slice ( colonIdx + 1 ) . trim ( )
146- if ( value === 'true' ) value = true
147- else if ( value === 'false' ) value = false
148- // Strip quotes
149- if (
150- typeof value === 'string' &&
151- ( ( value . startsWith ( '"' ) && value . endsWith ( '"' ) ) || ( value . startsWith ( "'" ) && value . endsWith ( "'" ) ) )
152- ) {
153- value = value . slice ( 1 , - 1 )
154- }
155- frontmatter [ key ] = value
156- }
157- return frontmatter
158- }
159-
160- function isPublished ( content : string ) : boolean {
161- const fm = parseFrontmatter ( content )
162- return fm . published !== false
163- }
164-
165128// --- Build valid paths from actual routes ---
166129
167130function discoverRoutes ( ) : Set < string > {
@@ -350,10 +313,6 @@ function cleanUrl(url: string): string {
350313 return url . split ( '?' ) [ 0 ] . split ( '#' ) [ 0 ] . replace ( / \/ $ / , '' )
351314}
352315
353- function isKnownRouteOrLocaleRedirect ( url : string , validPaths : Set < string > ) : boolean {
354- return validPaths . has ( url ) || REDIRECTED_LOCALE_PATH_PREFIX . test ( url )
355- }
356-
357316// --- Pass 1: Internal link validation ---
358317
359318function checkLinks ( validPaths : Set < string > ) {
@@ -364,7 +323,7 @@ function checkLinks(validPaths: Set<string>) {
364323 for ( const file of files ) {
365324 if ( ! isContentPage ( file ) ) continue
366325 const content = fs . readFileSync ( file , 'utf-8' )
367- if ( ! isPublished ( content ) ) {
326+ if ( ! isPublishedContent ( content ) ) {
368327 skippedUnpublished ++
369328 continue
370329 }
@@ -417,7 +376,7 @@ function checkPublishedHasRoute(validPaths: Set<string>) {
417376 const enFile = path . join ( CONTENT_DIR , ct . dir , slug , 'en.md' )
418377 if ( ! fs . existsSync ( enFile ) ) continue
419378 const content = fs . readFileSync ( enFile , 'utf-8' )
420- if ( ! isPublished ( content ) ) continue
379+ if ( ! isPublishedContent ( content ) ) continue
421380
422381 const url = ct . urlPattern ( 'en' , slug )
423382 if ( ! validPaths . has ( url ) ) {
@@ -432,7 +391,7 @@ function checkPublishedHasRoute(validPaths: Set<string>) {
432391 const enFile = path . join ( CONTENT_DIR , intent , 'en.md' )
433392 if ( ! fs . existsSync ( enFile ) ) continue
434393 const content = fs . readFileSync ( enFile , 'utf-8' )
435- if ( ! isPublished ( content ) ) continue
394+ if ( ! isPublishedContent ( content ) ) continue
436395
437396 const url = `/en/${ intent } `
438397 if ( ! validPaths . has ( url ) ) {
@@ -489,9 +448,9 @@ function checkFrontmatter() {
489448 for ( const file of files ) {
490449 if ( ! isContentPage ( file ) ) continue
491450 const content = fs . readFileSync ( file , 'utf-8' )
492- const fm = parseFrontmatter ( content )
451+ const fm = parseContentFrontmatter ( content )
493452
494- if ( ! isPublished ( content ) ) continue
453+ if ( ! isPublishedContent ( content ) ) continue
495454
496455 if ( ! fm . title || ( typeof fm . title === 'string' && fm . title . trim ( ) === '' ) ) {
497456 error ( 'frontmatter' , 'Published file missing title' , rel ( file ) )
@@ -520,7 +479,7 @@ function checkLocaleCoverage() {
520479 const enFile = path . join ( slugDir , 'en.md' )
521480 if ( ! fs . existsSync ( enFile ) ) continue
522481 const content = fs . readFileSync ( enFile , 'utf-8' )
523- if ( ! isPublished ( content ) ) continue
482+ if ( ! isPublishedContent ( content ) ) continue
524483
525484 for ( const locale of PRIMARY_LOCALES ) {
526485 if ( locale === 'en' ) continue
@@ -559,9 +518,9 @@ function checkContentPolish() {
559518 for ( const file of files ) {
560519 if ( ! isContentPage ( file ) ) continue
561520 const content = fs . readFileSync ( file , 'utf-8' )
562- if ( ! isPublished ( content ) ) continue
521+ if ( ! isPublishedContent ( content ) ) continue
563522
564- const fm = parseFrontmatter ( content )
523+ const fm = parseContentFrontmatter ( content )
565524
566525 // Frontmatter override: skip_polish_check: true bypasses this check
567526 if ( fm . skip_polish_check === true ) continue
@@ -604,7 +563,7 @@ function checkExplicitPublished() {
604563
605564 for ( const file of files ) {
606565 const content = fs . readFileSync ( file , 'utf-8' )
607- const fm = parseFrontmatter ( content )
566+ const fm = parseContentFrontmatter ( content )
608567
609568 if ( fm . published === false ) {
610569 warn ( 'draft-content' , 'File is explicitly unpublished (draft)' , rel ( file ) )
@@ -659,7 +618,7 @@ function checkPageCountRegression() {
659618 const files = getAllMdFiles ( CONTENT_DIR )
660619 const publishedCount = files . filter ( ( f ) => {
661620 const content = fs . readFileSync ( f , 'utf-8' )
662- return isPublished ( content )
621+ return isPublishedContent ( content )
663622 } ) . length
664623
665624 let baseline = 0
0 commit comments