@@ -9,6 +9,7 @@ import markdownItAnchor from "markdown-it-anchor";
99const __dirname = path . dirname ( fileURLToPath ( import . meta. url ) ) ;
1010const rootDir = path . resolve ( __dirname , ".." ) ;
1111const readmePath = path . join ( rootDir , "README.md" ) ;
12+ const versionPath = path . join ( rootDir , "VERSION" ) ;
1213const stylesPath = path . join ( rootDir , "website" , "site.css" ) ;
1314const publicDir = path . join ( rootDir , "public" ) ;
1415const distDir = path . join ( rootDir , "dist" ) ;
@@ -36,6 +37,7 @@ const emotionStyles = {
3637} ;
3738
3839const readme = await readFile ( readmePath , "utf8" ) ;
40+ const version = normalizeVersion ( await readFile ( versionPath , "utf8" ) ) ;
3941const styles = await readFile ( stylesPath , "utf8" ) ;
4042
4143const md = new MarkdownIt ( {
@@ -69,19 +71,22 @@ const quickAnswers = buildQuickAnswers(summary);
6971const keywords = buildKeywords ( ) ;
7072const buildDate = new Date ( ) ;
7173const dateModifiedIso = buildDate . toISOString ( ) ;
74+ const releaseTag = `v${ version } ` ;
7275const structuredData = buildStructuredData ( {
7376 dateModifiedIso,
7477 keywords,
7578 licenseUrl,
7679 quickAnswers,
7780 readmeUrl,
7881 repoUrl,
82+ releaseTag,
7983 sections,
8084 socialImageUrl,
8185 siteName,
8286 siteUrl,
8387 summary,
84- title
88+ title,
89+ version
8590} ) ;
8691const builtAt = new Intl . DateTimeFormat ( "en" , {
8792 dateStyle : "long" ,
@@ -101,6 +106,7 @@ const page = `<!DOCTYPE html>
101106 <title>${ escapeHtml ( title ) } </title>
102107 <meta name="description" content="${ escapeHtml ( summary ) } " />
103108 <meta name="keywords" content="${ escapeHtml ( keywords . join ( ", " ) ) } " />
109+ <meta name="version" content="${ escapeHtml ( version ) } " />
104110 <meta name="author" content="Managed Code" />
105111 <meta name="publisher" content="Managed Code" />
106112 <meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" />
@@ -186,6 +192,7 @@ const page = `<!DOCTYPE html>
186192 <article class="content-card" id="specification">
187193 <div class="content-meta" id="top">
188194 <span class="meta-chip">Source of truth <code>README.md</code></span>
195+ <span class="meta-chip">Version ${ escapeHtml ( releaseTag ) } </span>
189196 <span class="meta-chip">${ stats . sectionCount } sections / ${ stats . subsectionCount } subsections</span>
190197 <span class="meta-chip">Built ${ escapeHtml ( builtAt ) } UTC</span>
191198 </div>
@@ -209,7 +216,7 @@ await writeFile(path.join(distDir, "sitemap.xml"), buildSitemapXml(siteUrl, date
209216await writeFile ( path . join ( distDir , "robots.txt" ) , buildRobotsTxt ( siteUrl ) , "utf8" ) ;
210217await writeFile (
211218 path . join ( distDir , "llms.txt" ) ,
212- buildLlmsTxt ( { licenseUrl, quickAnswers, readmeUrl, repoUrl, siteUrl, stats, summary, title } ) ,
219+ buildLlmsTxt ( { licenseUrl, quickAnswers, readmeUrl, releaseTag , repoUrl, siteUrl, stats, summary, title, version } ) ,
213220 "utf8"
214221) ;
215222
@@ -386,12 +393,14 @@ function buildStructuredData({
386393 quickAnswers,
387394 readmeUrl,
388395 repoUrl,
396+ releaseTag,
389397 sections,
390398 socialImageUrl,
391399 siteName,
392400 siteUrl,
393401 summary,
394- title
402+ title,
403+ version
395404} ) {
396405 const primarySections = sections
397406 . filter ( ( section ) => section . depth === 2 )
@@ -406,6 +415,7 @@ function buildStructuredData({
406415 url : siteUrl ,
407416 name : siteName ,
408417 description : summary ,
418+ version,
409419 inLanguage : "en" ,
410420 publisher : {
411421 "@type" : "Organization" ,
@@ -419,6 +429,7 @@ function buildStructuredData({
419429 headline : title ,
420430 description : summary ,
421431 url : siteUrl ,
432+ version,
422433 mainEntityOfPage : siteUrl ,
423434 isPartOf : {
424435 "@id" : `${ siteUrl } #website`
@@ -445,7 +456,7 @@ function buildStructuredData({
445456 dateModified : dateModifiedIso ,
446457 inLanguage : "en" ,
447458 image : socialImageUrl ,
448- sameAs : [ repoUrl , readmeUrl ] ,
459+ sameAs : [ repoUrl , readmeUrl , ` ${ repoUrl } /releases/tag/ ${ releaseTag } ` ] ,
449460 speakable : {
450461 "@type" : "SpeakableSpecification" ,
451462 cssSelector : [ ".hero-summary" , ".answer-answer" ]
@@ -501,14 +512,16 @@ Sitemap: ${siteUrl}sitemap.xml
501512` ;
502513}
503514
504- function buildLlmsTxt ( { licenseUrl, quickAnswers, readmeUrl, repoUrl, siteUrl, stats, summary, title } ) {
515+ function buildLlmsTxt ( { licenseUrl, quickAnswers, readmeUrl, releaseTag , repoUrl, siteUrl, stats, summary, title, version } ) {
505516 return `# ${ title }
506517
507518> ${ summary }
508519
509520Canonical: ${ siteUrl }
510521Repository: ${ repoUrl }
511522Source of truth: ${ readmeUrl }
523+ Version: ${ version }
524+ Release tag: ${ releaseTag }
512525License: ${ licenseUrl }
513526
514527## Key Facts
@@ -552,3 +565,12 @@ function escapeHtml(value) {
552565function escapeRegExp ( value ) {
553566 return value . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, "\\$&" ) ;
554567}
568+
569+ function normalizeVersion ( value ) {
570+ const normalized = value . trim ( ) . replace ( / ^ v / i, "" ) ;
571+ if ( ! / ^ \d + \. \d + \. \d + (?: - [ 0 - 9 A - Z a - z . - ] + ) ? (?: \+ [ 0 - 9 A - Z a - z . - ] + ) ? $ / . test ( normalized ) ) {
572+ throw new Error ( `Invalid VERSION value: ${ value . trim ( ) } ` ) ;
573+ }
574+
575+ return normalized ;
576+ }
0 commit comments