@@ -57,13 +57,41 @@ async function generateOgImages(config: SiteConfig) {
5757 }
5858
5959 // Extract first H3 section title and its bullet points from a changelog body
60- function extractChangelogSnippet ( body : string | null | undefined ) : string | undefined {
60+ function extractChangelogSnippet ( body : string | null | undefined , options : { includeHeading ?: boolean } = { } ) : string | undefined {
61+ const { includeHeading = true } = options
6162 if ( ! body )
6263 return undefined
6364 const src = body . replace ( / \r / g, '' )
6465 // Cut off checksum sections to avoid noise
6566 const cleaned = src . split ( / - - - \n \n # # # \s * C h e c k s u m s | - - - \n \n M D 5 / i) [ 0 ] || src
66- // Find first H3 section
67+
68+ const esc = ( s : string ) => s . replace ( / & / g, '&' ) . replace ( / < / g, '<' )
69+
70+ if ( ! includeHeading ) {
71+ // For beta changelogs: extract first 3 bullet points from anywhere in the body
72+ let bullets = cleaned
73+ . split ( '\n' )
74+ . map ( l => l . trim ( ) )
75+ . filter ( l => / ^ [ - * ] \s + / . test ( l ) )
76+ . map ( l => l . replace ( / ^ [ - * ] \s + / , '' ) )
77+
78+ // Sanitize bullets: remove images, links (keep label), inline code, and parenthetical content
79+ bullets = bullets . map ( ( b ) => {
80+ const t = b
81+ . replace ( / ! \[ [ ^ \] ] * \] \( [ ^ ) ] * \) / g, '' )
82+ . replace ( / \[ ( [ ^ \] ] + ) \] \( [ ^ ) ] * \) / g, '$1' )
83+ . replace ( / ` ( [ ^ ` ] + ) ` / g, '$1' )
84+ . trim ( )
85+ return stripParens ( t )
86+ } ) . filter ( Boolean )
87+
88+ const top = bullets . slice ( 0 , 3 )
89+ if ( top . length === 0 )
90+ return undefined
91+ return top . map ( b => `<div>• ${ esc ( b ) } </div>` ) . join ( '' )
92+ }
93+
94+ // Find first H3 section for stable changelogs
6795 const match = cleaned . match ( / ^ # # # \s + ( \S [ ^ \n ] * ) \n ( [ \s \S ] * ?) (? = ^ # { 1 , 3 } \s | Z ) / m)
6896 if ( ! match )
6997 return undefined
@@ -76,7 +104,6 @@ async function generateOgImages(config: SiteConfig) {
76104 . map ( l => l . trim ( ) )
77105 . filter ( l => / ^ [ - * ] \s + / . test ( l ) )
78106 . map ( l => l . replace ( / ^ [ - * ] \s + / , '' ) )
79- const esc = ( s : string ) => s . replace ( / & / g, '&' ) . replace ( / < / g, '<' )
80107 if ( bullets . length === 0 ) {
81108 return `<div style="font-weight:700">${ esc ( heading . slice ( 0 , 220 ) ) } </div>`
82109 }
@@ -104,7 +131,7 @@ async function generateOgImages(config: SiteConfig) {
104131 return s . replace ( / \s * \( [ ^ ) ] * \) / g, '' ) . trim ( )
105132 }
106133
107- // Generate OG images for dynamic changelog pages
134+ // Generate OG images for dynamic changelog pages (stable)
108135 const octokit = new Octokit ( )
109136 const releases = await octokit . paginate ( octokit . repos . listReleases , {
110137 owner : 'mihonapp' ,
@@ -131,6 +158,33 @@ async function generateOgImages(config: SiteConfig) {
131158 fonts,
132159 } )
133160 }
161+
162+ // Generate OG images for dynamic changelog pages (beta)
163+ const betaReleases = await octokit . paginate ( octokit . repos . listReleases , {
164+ owner : 'mihonapp' ,
165+ repo : 'mihon-preview' ,
166+ per_page : 100 ,
167+ } )
168+
169+ for ( const r of betaReleases ) {
170+ if ( ! r . tag_name )
171+ continue
172+ const pageLike : Pick < ContentData , 'url' | 'frontmatter' > = {
173+ url : `/changelogs/beta/${ r . tag_name } ` ,
174+ frontmatter : {
175+ // Prefer release name; fallback to tag
176+ title : r . name || `Mihon ${ r . tag_name } ` ,
177+ description : extractChangelogSnippet ( r . body , { includeHeading : false } ) ,
178+ } as any ,
179+ }
180+
181+ await generateImage ( {
182+ page : pageLike ,
183+ template,
184+ outDir : config . outDir ,
185+ fonts,
186+ } )
187+ }
134188}
135189
136190export default generateOgImages
0 commit comments