55 * This eliminates runtime fs calls, making it compatible with Cloudflare Edge
66 */
77
8+ import { execSync } from 'node:child_process'
89import fs from 'node:fs'
910import path from 'node:path'
1011import { fileURLToPath } from 'node:url'
@@ -247,10 +248,14 @@ function generateArticleComponentsCode(articles) {
247248 for ( const locale of locales ) {
248249 const localeArticles = articles [ locale ]
249250 const componentEntries = localeArticles
250- . map (
251- article =>
252- ` '${ article . slug } ': () => import('@content/articles/${ locale } /${ article . slug } .mdx'),`
253- )
251+ . map ( article => {
252+ const importLine = ` '${ article . slug } ': () => import('@content/articles/${ locale } /${ article . slug } .mdx'),`
253+ // Only split if line is very long (>100 chars)
254+ if ( importLine . length > 100 ) {
255+ return ` '${ article . slug } ': () =>\n import('@content/articles/${ locale } /${ article . slug } .mdx'),`
256+ }
257+ return importLine
258+ } )
254259 . join ( '\n' )
255260
256261 if ( componentEntries ) {
@@ -260,7 +265,10 @@ function generateArticleComponentsCode(articles) {
260265 }
261266 }
262267
263- return `const articleComponents: Record<string, Record<string, () => Promise<{ default: React.ComponentType }>>> = {\n${ componentLines . join ( '\n' ) } \n}`
268+ return `const articleComponents: Record<
269+ string,
270+ Record<string, () => Promise<{ default: React.ComponentType }>>
271+ > = {\n${ componentLines . join ( '\n' ) } \n}`
264272}
265273
266274// Generate component imports for docs
@@ -271,7 +279,11 @@ function generateDocComponentsCode(docs) {
271279 for ( const locale of locales ) {
272280 const localeDocs = docs [ locale ]
273281 const componentEntries = localeDocs
274- . map ( doc => ` '${ doc . slug } ': () => import('@content/docs/${ locale } /${ doc . slug } .mdx'),` )
282+ . map ( doc => {
283+ // Use unquoted keys for simple identifiers
284+ const key = / ^ [ a - z A - Z _ ] [ a - z A - Z 0 - 9 _ ] * $ / . test ( doc . slug ) ? doc . slug : `'${ doc . slug } '`
285+ return ` ${ key } : () => import('@content/docs/${ locale } /${ doc . slug } .mdx'),`
286+ } )
275287 . join ( '\n' )
276288
277289 if ( componentEntries ) {
@@ -281,7 +293,10 @@ function generateDocComponentsCode(docs) {
281293 }
282294 }
283295
284- return `const docComponents: Record<string, Record<string, () => Promise<{ default: React.ComponentType }>>> = {\n${ componentLines . join ( '\n' ) } \n}`
296+ return `const docComponents: Record<
297+ string,
298+ Record<string, () => Promise<{ default: React.ComponentType }>>
299+ > = {\n${ componentLines . join ( '\n' ) } \n}`
285300}
286301
287302// Generate component import for manifesto (single index.mdx per locale)
@@ -323,22 +338,66 @@ function main() {
323338
324339 const header = `// This file is auto-generated by scripts/generate-metadata.mjs\n// DO NOT EDIT MANUALLY`
325340
341+ // Custom JSON stringify that uses single quotes and unquoted keys
342+ function formatObject ( obj , indent = 0 ) {
343+ const spaces = ' ' . repeat ( indent )
344+ const innerSpaces = ' ' . repeat ( indent + 1 )
345+
346+ if ( Array . isArray ( obj ) ) {
347+ if ( obj . length === 0 ) return '[]'
348+ const items = obj . map ( item => `${ innerSpaces } ${ formatObject ( item , indent + 1 ) } ` ) . join ( ',\n' )
349+ return `[\n${ items } ,\n${ spaces } ]`
350+ }
351+
352+ if ( obj !== null && typeof obj === 'object' ) {
353+ const keys = Object . keys ( obj )
354+ if ( keys . length === 0 ) return '{}'
355+ const items = keys
356+ . map ( key => {
357+ const quotedKey =
358+ / ^ [ a - z A - Z _ ] [ a - z A - Z 0 - 9 _ ] * $ / . test ( key ) && ! key . includes ( '-' ) ? key : `'${ key } '`
359+ const value = formatObject ( obj [ key ] , indent + 1 )
360+ // Check if the line would be too long (>80 chars) and split if needed
361+ const fullLine = `${ innerSpaces } ${ quotedKey } : ${ value } `
362+ if ( fullLine . length > 80 && typeof obj [ key ] === 'string' ) {
363+ return `${ innerSpaces } ${ quotedKey } :\n${ innerSpaces } ${ value } `
364+ }
365+ return fullLine
366+ } )
367+ . join ( ',\n' )
368+ return `{\n${ items } ,\n${ spaces } }`
369+ }
370+
371+ if ( typeof obj === 'string' ) {
372+ // Escape backslashes first, then escape single quotes, then escape newlines
373+ const escaped = obj
374+ . replace ( / \\ / g, '\\\\' )
375+ . replace ( / ' / g, "\\'" )
376+ . replace ( / \n / g, '\\n' )
377+ . replace ( / \r / g, '\\r' )
378+ . replace ( / \t / g, '\\t' )
379+ return `'${ escaped } '`
380+ }
381+
382+ return JSON . stringify ( obj )
383+ }
384+
326385 const content = `${ header }
327386
328- import type { ArticleMetadata } from './articles';
329- import type { DocSection } from './docs';
330- import type { CollectionSection } from '../collections';
331- import type { FaqItem } from '../faq';
387+ import type { CollectionSection } from '../collections'
388+ import type { FaqItem } from '../faq'
389+ import type { ArticleMetadata } from './articles'
390+ import type { DocSection } from './docs'
332391
333- export const articlesMetadata: Record<string, ArticleMetadata[]> = ${ JSON . stringify ( articles , null , 2 ) } ;
392+ export const articlesMetadata: Record<string, ArticleMetadata[]> = ${ formatObject ( articles ) }
334393
335- export const docsMetadata: Record<string, DocSection[]> = ${ JSON . stringify ( docs , null , 2 ) } ;
394+ export const docsMetadata: Record<string, DocSection[]> = ${ formatObject ( docs ) }
336395
337- export const collectionsMetadata: Record<string, CollectionSection> = ${ JSON . stringify ( collections , null , 2 ) } ;
396+ export const collectionsMetadata: Record<string, CollectionSection> = ${ formatObject ( collections ) }
338397
339- export const faqMetadata: Record<string, FaqItem[]> = ${ JSON . stringify ( faqs , null , 2 ) } ;
398+ export const faqMetadata: Record<string, FaqItem[]> = ${ formatObject ( faqs ) }
340399
341- export const stackCounts: Record<string, number> = ${ JSON . stringify ( stackCounts , null , 2 ) } ;
400+ export const stackCounts: Record<string, number> = ${ formatObject ( stackCounts ) }
342401`
343402
344403 fs . writeFileSync ( outputFile , content , 'utf8' )
@@ -348,7 +407,7 @@ export const stackCounts: Record<string, number> = ${JSON.stringify(stackCounts,
348407 const articlesComponentsCode = generateArticleComponentsCode ( articles )
349408 const articlesGeneratedContent = `${ header }
350409
351- import { articlesMetadata } from './metadata';
410+ import { articlesMetadata } from './metadata'
352411
353412export type ArticleMetadata = {
354413 title: string
@@ -375,7 +434,10 @@ export function getArticleBySlug(slug: string, locale: string = 'en'): ArticleMe
375434${ articlesComponentsCode }
376435
377436// Get a specific article component for a given locale and slug
378- export async function getArticleComponent(locale: string = 'en', slug: string): Promise<React.ComponentType | null> {
437+ export async function getArticleComponent(
438+ locale: string = 'en',
439+ slug: string
440+ ): Promise<React.ComponentType | null> {
379441 const loaders = articleComponents[locale] || articleComponents.en
380442 const loader = loaders?.[slug]
381443 if (!loader) return null
@@ -391,7 +453,7 @@ export async function getArticleComponent(locale: string = 'en', slug: string):
391453 const docsComponentsCode = generateDocComponentsCode ( docs )
392454 const docsGeneratedContent = `${ header }
393455
394- import { docsMetadata } from './metadata';
456+ import { docsMetadata } from './metadata'
395457
396458export type DocSection = {
397459 id: string
@@ -417,7 +479,10 @@ export function getDocBySlug(slug: string, locale: string = 'en'): DocSection |
417479${ docsComponentsCode }
418480
419481// Get a specific doc component for a given locale and slug
420- export async function getDocComponent(locale: string = 'en', slug: string): Promise<React.ComponentType | null> {
482+ export async function getDocComponent(
483+ locale: string = 'en',
484+ slug: string
485+ ): Promise<React.ComponentType | null> {
421486 const loaders = docComponents[locale] || docComponents.en
422487 const loader = loaders?.[slug]
423488 if (!loader) return null
@@ -441,6 +506,9 @@ export async function getManifestoComponent(locale: string = 'en'): Promise<Reac
441506${ manifestoComponentsCode }
442507
443508 const loader = components[locale] || components.en
509+ if (!loader) {
510+ throw new Error(\`No manifesto loader found for locale: \${locale}\`)
511+ }
444512 const mdxModule = await loader()
445513 return mdxModule.default
446514}
@@ -478,6 +546,18 @@ ${manifestoComponentsCode}
478546 console . log ( ` - ${ path . relative ( rootDir , articlesGeneratedFile ) } ` )
479547 console . log ( ` - ${ path . relative ( rootDir , docsGeneratedFile ) } ` )
480548 console . log ( ` - ${ path . relative ( rootDir , manifestoGeneratedFile ) } ` )
549+
550+ // Run biome formatting on generated files
551+ console . log ( `\n🎨 Formatting generated files with Biome...` )
552+ try {
553+ execSync ( `npx biome format --write ${ outputDir } ` , {
554+ cwd : rootDir ,
555+ stdio : 'inherit' ,
556+ } )
557+ console . log ( `✅ Formatting complete` )
558+ } catch ( error ) {
559+ console . error ( `⚠️ Biome formatting failed:` , error . message )
560+ }
481561}
482562
483563main ( )
0 commit comments