// Genera l'indice dei file di contenuto in src/content/generated/ (gli import li scrive lui, non a mano). // Uso: node scripts/content-index.mjs → (ri)scrive l'indice // node scripts/content-index.mjs --check → exit 1 se l'indice non è aggiornato (usato dai test) // Parte da solo prima di dev, build, test e typecheck (script "pre*" in package.json). import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs' import { dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' const root = join(dirname(fileURLToPath(import.meta.url)), '..', 'src', 'content') const outDir = join(root, 'generated') const check = process.argv.includes('--check') const SLUG = /^[a-z0-9]+(-[a-z0-9]+)*$/ /** File .ts di una cartella (slug = nome senza estensione), ordinati. */ function slugsIn(dir) { if (!existsSync(dir)) return [] return readdirSync(dir) .filter((f) => f.endsWith('.ts') && !f.endsWith('.spec.ts')) .map((f) => f.slice(0, -3)) .sort() .map((slug) => { if (!SLUG.test(slug)) throw new Error(`Nome file non valido: ${join(dir, slug)}.ts (solo a-z, 0-9 e "-")`) return slug }) } const ident = (...parts) => parts.join('_').replace(/-/g, '_') const areas = slugsIn(join(root, 'areas')) const solutionsDir = join(root, 'solutions') const solutionAreas = existsSync(solutionsDir) ? readdirSync(solutionsDir).filter((d) => statSync(join(solutionsDir, d)).isDirectory()).sort() : [] const solutions = solutionAreas.flatMap((area) => { if (!areas.includes(area)) throw new Error(`src/content/solutions/${area}/: non esiste la macro-area src/content/areas/${area}.ts`) return slugsIn(join(solutionsDir, area)).map((slug) => ({ area, slug })) }) const companyPages = slugsIn(join(root, 'company')) const header = '// File generato da scripts/content-index.mjs: non modificare a mano.\n' const files = { 'paths.ts': header + '// Solo slug (dai nomi dei file), senza importare i contenuti: per le route e la sitemap.\n\n' + `export const AREA_SLUGS: readonly string[] = ${JSON.stringify(areas)}\n\n` + `export const SOLUTION_PATHS: readonly { area: string; slug: string }[] = ${JSON.stringify(solutions)}\n\n` + `export const COMPANY_PAGE_SLUGS: readonly string[] = ${JSON.stringify(companyPages)}\n`, 'areas.ts': header + "import type { AreaContent } from '../types'\n" + areas.map((s) => `import ${ident('area', s)} from '../areas/${s}'\n`).join('') + `\nexport const AREA_FILES: readonly AreaContent[] = [${areas.map((s) => ident('area', s)).join(', ')}]\n`, 'solutions.ts': header + "import type { SolutionContent } from '../types'\n" + solutions.map(({ area, slug }) => `import ${ident('solution', area, slug)} from '../solutions/${area}/${slug}'\n`).join('') + `\nexport const SOLUTION_FILES: readonly SolutionContent[] = [\n${solutions .map(({ area, slug }) => ` ${ident('solution', area, slug)},\n`) .join('')}]\n`, 'company.ts': header + "import type { CompanyPageContent } from '../types'\n" + companyPages.map((s) => `import ${ident('company', s)} from '../company/${s}'\n`).join('') + `\nexport const COMPANY_PAGE_FILES: readonly CompanyPageContent[] = [${companyPages.map((s) => ident('company', s)).join(', ')}]\n`, } let stale = [] if (!check) mkdirSync(outDir, { recursive: true }) for (const [name, content] of Object.entries(files)) { const path = join(outDir, name) const current = existsSync(path) ? readFileSync(path, 'utf8') : null if (current === content) continue if (check) stale.push(name) else writeFileSync(path, content) } if (check && stale.length) { console.error(`Indice dei contenuti non aggiornato (${stale.join(', ')}): esegui "npm run content -w apps/public"`) process.exit(1) } console.log( `Contenuti: ${areas.length} aree, ${solutions.length} soluzioni, ${companyPages.length} pagine azienda${check ? ' (indice aggiornato)' : ''}`, )