@lastbrain/app 2.0.31 → 2.0.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/analytics/registry.d.ts +7 -0
  2. package/dist/analytics/registry.d.ts.map +1 -0
  3. package/dist/analytics/registry.js +11 -0
  4. package/dist/auth/useAuthSession.d.ts.map +1 -1
  5. package/dist/auth/useAuthSession.js +85 -1
  6. package/dist/cli.js +19 -3
  7. package/dist/components/LanguageSwitcher.d.ts.map +1 -1
  8. package/dist/components/LanguageSwitcher.js +89 -5
  9. package/dist/config/version.d.ts.map +1 -1
  10. package/dist/config/version.js +30 -19
  11. package/dist/i18n/useLink.d.ts.map +1 -1
  12. package/dist/i18n/useLink.js +15 -0
  13. package/dist/index.d.ts +3 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +4 -0
  16. package/dist/layouts/AdminLayoutWithSidebar.d.ts +3 -1
  17. package/dist/layouts/AdminLayoutWithSidebar.d.ts.map +1 -1
  18. package/dist/layouts/AdminLayoutWithSidebar.js +2 -2
  19. package/dist/layouts/AppProviders.d.ts +7 -1
  20. package/dist/layouts/AppProviders.d.ts.map +1 -1
  21. package/dist/layouts/AppProviders.js +24 -3
  22. package/dist/layouts/AuthLayout.js +1 -1
  23. package/dist/layouts/PublicLayout.js +1 -1
  24. package/dist/layouts/RootLayout.d.ts.map +1 -1
  25. package/dist/scripts/init-app.d.ts.map +1 -1
  26. package/dist/scripts/init-app.js +301 -138
  27. package/dist/scripts/module-build.d.ts.map +1 -1
  28. package/dist/scripts/module-build.js +402 -67
  29. package/dist/scripts/module-create.d.ts.map +1 -1
  30. package/dist/scripts/module-create.js +227 -10
  31. package/dist/scripts/sitemap-flat-generator.d.ts +39 -0
  32. package/dist/scripts/sitemap-flat-generator.d.ts.map +1 -0
  33. package/dist/scripts/sitemap-flat-generator.js +231 -0
  34. package/dist/scripts/sitemap-manifest-generator.d.ts +59 -0
  35. package/dist/scripts/sitemap-manifest-generator.d.ts.map +1 -0
  36. package/dist/scripts/sitemap-manifest-generator.js +290 -0
  37. package/dist/sitemap/manifest.d.ts +8 -0
  38. package/dist/sitemap/manifest.d.ts.map +1 -0
  39. package/dist/sitemap/manifest.js +6 -0
  40. package/dist/styles.css +2 -2
  41. package/dist/templates/AuthGuidePage.js +2 -0
  42. package/dist/templates/DefaultDoc.d.ts.map +1 -1
  43. package/dist/templates/DefaultDoc.js +9 -5
  44. package/dist/templates/DocPage.d.ts.map +1 -1
  45. package/dist/templates/DocPage.js +40 -0
  46. package/dist/templates/MigrationsGuidePage.js +2 -0
  47. package/dist/templates/ModuleGuidePage.d.ts.map +1 -1
  48. package/dist/templates/ModuleGuidePage.js +4 -1
  49. package/dist/templates/SimpleHomePage.js +2 -0
  50. package/package.json +11 -4
  51. package/src/analytics/registry.ts +14 -0
  52. package/src/auth/useAuthSession.ts +91 -1
  53. package/src/cli.ts +19 -3
  54. package/src/components/LanguageSwitcher.tsx +113 -23
  55. package/src/config/version.ts +30 -19
  56. package/src/i18n/useLink.ts +15 -0
  57. package/src/index.ts +17 -0
  58. package/src/layouts/AdminLayoutWithSidebar.tsx +4 -0
  59. package/src/layouts/AppProviders.tsx +66 -8
  60. package/src/layouts/AuthLayout.tsx +1 -1
  61. package/src/layouts/PublicLayout.tsx +1 -1
  62. package/src/layouts/RootLayout.tsx +0 -1
  63. package/src/scripts/init-app.ts +360 -149
  64. package/src/scripts/module-build.ts +458 -72
  65. package/src/scripts/module-create.ts +260 -10
  66. package/src/scripts/sitemap-flat-generator.ts +313 -0
  67. package/src/scripts/sitemap-manifest-generator.ts +476 -0
  68. package/src/sitemap/manifest.ts +17 -0
  69. package/src/templates/AuthGuidePage.tsx +1 -1
  70. package/src/templates/DefaultDoc.tsx +397 -6
  71. package/src/templates/DocPage.tsx +40 -0
  72. package/src/templates/MigrationsGuidePage.tsx +1 -1
  73. package/src/templates/ModuleGuidePage.tsx +3 -2
  74. package/src/templates/SimpleHomePage.tsx +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"module-create.d.ts","sourceRoot":"","sources":["../../src/scripts/module-create.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,CAAC,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC;CAC3C;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AA61CD;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAiB1C;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,MAAM,iBA2MhB;AAED;;GAEG;AACH,wBAAsB,YAAY,kBAuOjC"}
1
+ {"version":3,"file":"module-create.d.ts","sourceRoot":"","sources":["../../src/scripts/module-create.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,CAAC,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC;CAC3C;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAu2CD;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAiB1C;AAoLD;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,MAAM,iBAyQhB;AAED;;GAEG;AACH,wBAAsB,YAAY,kBAuOjC"}
@@ -28,13 +28,14 @@ function parsePagesList(input) {
28
28
  }
29
29
  /**
30
30
  * Slugifie un nom de page: minuscules, tirets, alphanum uniquement
31
+ * Préserve les crochets [] pour les routes dynamiques et les slashes / pour les sous-chemins
31
32
  */
32
33
  function slugifyPageName(name) {
33
34
  return name
34
35
  .trim()
35
36
  .toLowerCase()
36
37
  .replace(/[\s_]+/g, "-")
37
- .replace(/[^a-z0-9-]/g, "")
38
+ .replace(/[^a-z0-9\-/[\]]/g, "") // Préserve -, /, [, ]
38
39
  .replace(/--+/g, "-")
39
40
  .replace(/^-+|-+$/g, "");
40
41
  }
@@ -75,7 +76,7 @@ function getLastBrainPackageVersions(rootDir) {
75
76
  */
76
77
  function generatePackageJson(moduleName, slug, rootDir, isPro = false) {
77
78
  const versions = getLastBrainPackageVersions(rootDir);
78
- const moduleNameOnly = slug.replace("module-", "").replace("-pro", "");
79
+ const moduleNameOnly = slug.replace("module-", "");
79
80
  const buildConfigExport = `./${moduleNameOnly}.build.config`;
80
81
  const packageJson = {
81
82
  name: moduleName,
@@ -329,7 +330,7 @@ function toPascalCase(value) {
329
330
  .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
330
331
  .join("");
331
332
  }
332
- function generateIndexTs(pages, moduleNameOnly) {
333
+ function generateIndexTs(pages, moduleNameOnly, hasPublicPages) {
333
334
  // Grouper les pages par nom pour détecter les doublons
334
335
  const pagesByName = new Map();
335
336
  for (const page of pages) {
@@ -351,6 +352,10 @@ function generateIndexTs(pages, moduleNameOnly) {
351
352
  // Nettoyer le nom pour l'alias d'export (enlever -pro si présent)
352
353
  const cleanedNameOnly = moduleNameOnly.replace(/-pro$/, "");
353
354
  const moduleAlias = toPascalCase(cleanedNameOnly);
355
+ // Export du sitemap manifest si le module a des pages publiques
356
+ const sitemapExport = hasPublicPages
357
+ ? `\n// Sitemap configuration\nexport { sitemapManifest } from "./sitemap/manifest";\n`
358
+ : "";
354
359
  return `// Client Components
355
360
  ${exports.join("\n")}
356
361
 
@@ -359,7 +364,7 @@ export { Doc } from "./components/Doc";
359
364
  export { Doc as ${moduleAlias}ModuleDoc } from "./components/Doc";
360
365
 
361
366
  // Configuration de build
362
- export { default as buildConfig } from "./${moduleNameOnly}.build.config";
367
+ export { default as buildConfig } from "./${moduleNameOnly}.build.config";${sitemapExport}
363
368
  `;
364
369
  }
365
370
  /**
@@ -1223,6 +1228,182 @@ export function findWorkspaceRoot() {
1223
1228
  }
1224
1229
  throw new Error("Impossible de trouver le répertoire racine du workspace (pnpm-workspace.yaml non trouvé)");
1225
1230
  }
1231
+ /**
1232
+ * Génère le fichier manifest.ts pour les sitemaps
1233
+ */
1234
+ function generateSitemapManifest(moduleName) {
1235
+ return `/**
1236
+ * Types pour le système de sitemaps multi-modules
1237
+ */
1238
+ export type SitemapChildKind = "static" | "reexport";
1239
+
1240
+ export interface SitemapChild {
1241
+ id: string;
1242
+ path: string;
1243
+ kind: SitemapChildKind;
1244
+ handler?: string;
1245
+ paging?: {
1246
+ pageSize: number;
1247
+ };
1248
+ }
1249
+
1250
+ export interface SitemapManifest {
1251
+ module: string;
1252
+ enabled: boolean;
1253
+ includePublicPagesFromBuildConfig: boolean;
1254
+ children: SitemapChild[];
1255
+ }
1256
+
1257
+ /**
1258
+ * Manifest des sitemaps du module ${moduleName}
1259
+ * Déclare tous les sitemaps enfants à générer
1260
+ *
1261
+ * Configuration:
1262
+ * - enabled: Active/désactive le module dans les sitemaps globaux
1263
+ * - includePublicPagesFromBuildConfig: Inclut automatiquement les pages publiques du build.config
1264
+ * - children: Liste des sitemaps à générer
1265
+ *
1266
+ * Types de sitemaps:
1267
+ * 1. "static": Pages statiques générées depuis build.config (sans paramètres dynamiques)
1268
+ * 2. "reexport": Sitemaps dynamiques avec handler personnalisé (avec paramètres :lang, :type, etc.)
1269
+ */
1270
+ export const sitemapManifest: SitemapManifest = {
1271
+ module: "${moduleName}",
1272
+ enabled: true,
1273
+ includePublicPagesFromBuildConfig: true,
1274
+ children: [
1275
+ // Pages statiques (générées automatiquement depuis build.config)
1276
+ {
1277
+ id: "static",
1278
+ path: ":lang/static.xml",
1279
+ kind: "static",
1280
+ },
1281
+
1282
+ // EXEMPLE: Sitemap dynamique avec handler personnalisé
1283
+ // Décommentez et adaptez selon vos besoins:
1284
+
1285
+ // {
1286
+ // id: "dynamic-content",
1287
+ // path: ":lang/content.xml",
1288
+ // kind: "reexport",
1289
+ // handler: "${moduleName}/sitemap/handlers/content",
1290
+ // },
1291
+ ],
1292
+ } as const;
1293
+ `;
1294
+ }
1295
+ /**
1296
+ * Génère un exemple de handler de sitemap dynamique
1297
+ */
1298
+ function generateSitemapHandler(moduleName) {
1299
+ return `/**
1300
+ * EXEMPLE: Handler dynamique pour sitemap
1301
+ *
1302
+ * Ce fichier montre comment créer un sitemap dynamique qui génère
1303
+ * des URLs à partir de données (base de données, API, liste statique, etc.)
1304
+ *
1305
+ * Pour activer ce handler:
1306
+ * 1. Décommentez l'entrée correspondante dans manifest.ts
1307
+ * 2. Adaptez la logique ci-dessous à vos besoins
1308
+ * 3. Rebuild le module: pnpm --filter ${moduleName} build
1309
+ */
1310
+
1311
+ import { LOCALE_MAP } from "@lastbrain/core";
1312
+ // Pour récupérer des données depuis Supabase:
1313
+ // import { getSupabaseServiceClient } from "@lastbrain/core/server";
1314
+
1315
+ /**
1316
+ * Handler GET pour générer le sitemap XML
1317
+ *
1318
+ * @param _request - Requête HTTP (non utilisée ici)
1319
+ * @param context - Contient les paramètres dynamiques de la route (:lang, :type, etc.)
1320
+ * @returns Response avec le XML du sitemap
1321
+ */
1322
+ export async function GET(
1323
+ _request: Request,
1324
+ context: { params: Promise<{ lang: string }> }
1325
+ ): Promise<Response> {
1326
+ const params = await context.params;
1327
+ const { lang } = params;
1328
+
1329
+ // Validation de la langue
1330
+ if (!LOCALE_MAP[lang]) {
1331
+ return new Response("Invalid language", { status: 404 });
1332
+ }
1333
+
1334
+ // Configuration de l'URL de base
1335
+ const baseUrl =
1336
+ process.env.NEXT_PUBLIC_SITE_URL ||
1337
+ (process.env.VERCEL_URL
1338
+ ? \`https://\${process.env.VERCEL_URL}\`
1339
+ : "https://example.com");
1340
+
1341
+ try {
1342
+ // EXEMPLE 1: Liste statique d'URLs
1343
+ const staticItems = [
1344
+ { slug: "item-1", updatedAt: new Date().toISOString() },
1345
+ { slug: "item-2", updatedAt: new Date().toISOString() },
1346
+ { slug: "item-3", updatedAt: new Date().toISOString() },
1347
+ ];
1348
+
1349
+ // EXEMPLE 2: Récupération depuis Supabase (décommentez si besoin)
1350
+ /*
1351
+ const supabase = getSupabaseServiceClient();
1352
+ const { data: items, error } = await supabase
1353
+ .from("your_table")
1354
+ .select("slug, updated_at")
1355
+ .eq("status", "published")
1356
+ .order("updated_at", { ascending: false })
1357
+ .limit(1000);
1358
+
1359
+ if (error) {
1360
+ console.error("Sitemap error:", error);
1361
+ return new Response(JSON.stringify({ error: error.message }), {
1362
+ status: 500,
1363
+ headers: { "Content-Type": "application/json" },
1364
+ });
1365
+ }
1366
+ */
1367
+
1368
+ // Génération des URLs pour le sitemap
1369
+ const urls = staticItems.map((item) => ({
1370
+ url: \`\${baseUrl}/\${lang}/your-section/\${item.slug}\`,
1371
+ lastmod: item.updatedAt,
1372
+ changefreq: "weekly",
1373
+ priority: 0.7,
1374
+ }));
1375
+
1376
+ // Construction du XML
1377
+ const urlEntries = urls
1378
+ .map(
1379
+ (entry) => \` <url>
1380
+ <loc>\${entry.url}</loc>
1381
+ <lastmod>\${entry.lastmod}</lastmod>
1382
+ <changefreq>\${entry.changefreq}</changefreq>
1383
+ <priority>\${entry.priority}</priority>
1384
+ </url>\`
1385
+ )
1386
+ .join("\\n");
1387
+
1388
+ const xml = \`<?xml version="1.0" encoding="UTF-8"?>
1389
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
1390
+ \${urlEntries}
1391
+ </urlset>\`;
1392
+
1393
+ // Retour de la réponse XML
1394
+ return new Response(xml, {
1395
+ headers: {
1396
+ "Content-Type": "application/xml",
1397
+ "Cache-Control": "public, max-age=3600",
1398
+ },
1399
+ });
1400
+ } catch (err) {
1401
+ console.error("Sitemap generation error:", err);
1402
+ return new Response("Internal error", { status: 500 });
1403
+ }
1404
+ }
1405
+ `;
1406
+ }
1226
1407
  /**
1227
1408
  * Crée la structure du module
1228
1409
  */
@@ -1248,9 +1429,11 @@ export async function createModuleStructure(config, rootDir) {
1248
1429
  const buildConfigFileName = `${moduleNameOnly}.build.config.ts`;
1249
1430
  console.log(chalk.yellow(` 📄 src/${buildConfigFileName}`));
1250
1431
  await fs.writeFile(path.join(moduleDir, "src", buildConfigFileName), generateBuildConfig(config));
1432
+ // Détecter si le module a des pages publiques (pour l'export sitemap)
1433
+ const hasPublicPages = config.pages.some((p) => p.section === "public");
1251
1434
  // Créer index.ts
1252
1435
  console.log(chalk.yellow(" 📄 src/index.ts"));
1253
- await fs.writeFile(path.join(moduleDir, "src", "index.ts"), generateIndexTs(config.pages, moduleNameOnly));
1436
+ await fs.writeFile(path.join(moduleDir, "src", "index.ts"), generateIndexTs(config.pages, moduleNameOnly, hasPublicPages));
1254
1437
  // Note: server.ts n'est plus généré pour éviter les conflits d'exports
1255
1438
  // Les routes API sont exposées via le build.config et l'app les importe dynamiquement
1256
1439
  // Créer Doc.tsx
@@ -1267,20 +1450,38 @@ export async function createModuleStructure(config, rootDir) {
1267
1450
  pagesByName.set(key, existing);
1268
1451
  }
1269
1452
  for (const page of config.pages) {
1270
- const pagePath = path.join(moduleDir, "src", "web", page.section);
1453
+ // Extraire le chemin du répertoire et le nom de base de la page
1454
+ const pageNameParts = page.name.split("/");
1455
+ const pageBaseName = pageNameParts[pageNameParts.length - 1];
1456
+ // Créer le chemin complet incluant les sous-répertoires avec leurs noms originaux
1457
+ // Garder les noms tels quels (ex: "apis", "[slug]" etc.) pour les répertoires
1458
+ const subdirParts = pageNameParts.slice(0, -1);
1459
+ const pagePath = subdirParts.length > 0
1460
+ ? path.join(moduleDir, "src", "web", page.section, ...subdirParts)
1461
+ : path.join(moduleDir, "src", "web", page.section);
1271
1462
  await fs.ensureDir(pagePath);
1272
- const baseName = page.name
1463
+ // Le nom de fichier suit la convention Next.js : garder les crochets
1464
+ // apis/[slug] → apis/[slug].tsx
1465
+ // apis/[id]/versions → apis/[id]/versions.tsx
1466
+ const fileName = `${pageBaseName}.tsx`;
1467
+ // Pour le nom du composant React, on enlève les crochets
1468
+ // [slug] → Slug, [id] → Id
1469
+ const cleanedBaseName = pageBaseName.replace(/[[\]]/g, "");
1470
+ const baseName = cleanedBaseName
1273
1471
  .split("-")
1274
1472
  .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
1275
1473
  .join("");
1276
- // Si le même nom de page existe dans plusieurs sections, ajouter la section au nom du fichier
1474
+ // Si le même nom de page existe dans plusieurs sections, ajouter la section au nom du composant
1277
1475
  const duplicates = pagesByName.get(page.name) || [];
1278
1476
  const hasDuplicates = duplicates.length > 1;
1279
1477
  const componentNameWithSection = hasDuplicates
1280
1478
  ? `${baseName}${toPascalCase(page.section)}`
1281
1479
  : baseName;
1282
- const fileName = `${componentNameWithSection}Page.tsx`;
1283
- console.log(chalk.yellow(` 📄 src/web/${page.section}/${fileName}`));
1480
+ // Afficher le chemin complet
1481
+ const fullDisplayPath = subdirParts.length > 0
1482
+ ? `src/web/${page.section}/${subdirParts.join("/")}/${fileName}`
1483
+ : `src/web/${page.section}/${fileName}`;
1484
+ console.log(chalk.yellow(` 📄 ${fullDisplayPath}`));
1284
1485
  await fs.writeFile(path.join(pagePath, fileName), generatePageComponent(page.name, page.section, componentNameWithSection));
1285
1486
  }
1286
1487
  // Créer les routes API
@@ -1315,6 +1516,22 @@ export async function createModuleStructure(config, rootDir) {
1315
1516
  console.log(chalk.yellow(` 📄 supabase/migrations-down/${downFileName}`));
1316
1517
  await fs.writeFile(path.join(moduleDir, "supabase", "migrations-down", downFileName), `-- DOWN migration for ${config.slug}\n${downContent}`);
1317
1518
  }
1519
+ // Créer les fichiers sitemap si le module a des pages publiques
1520
+ if (hasPublicPages) {
1521
+ console.log(chalk.blue("\n🗺️ Création des fichiers sitemap..."));
1522
+ const sitemapDir = path.join(moduleDir, "src", "sitemap");
1523
+ const handlersDir = path.join(sitemapDir, "handlers");
1524
+ await fs.ensureDir(handlersDir);
1525
+ // manifest.ts
1526
+ console.log(chalk.yellow(" 📄 src/sitemap/manifest.ts"));
1527
+ await fs.writeFile(path.join(sitemapDir, "manifest.ts"), generateSitemapManifest(config.slug.replace("module-", "")));
1528
+ // Exemple de handler dynamique (commenté par défaut)
1529
+ console.log(chalk.yellow(" 📄 src/sitemap/handlers/example.ts (commented)"));
1530
+ await fs.writeFile(path.join(handlersDir, "example.ts"), generateSitemapHandler(config.moduleName));
1531
+ console.log(chalk.green(" ✓ Fichiers sitemap créés"));
1532
+ console.log(chalk.gray(" → Éditez manifest.ts pour personnaliser vos sitemaps"));
1533
+ console.log(chalk.gray(" → Décommentez handlers/example.ts pour ajouter des sitemaps dynamiques"));
1534
+ }
1318
1535
  // Générer la documentation du module
1319
1536
  console.log(chalk.blue("\n📝 Génération de la documentation..."));
1320
1537
  await generateModuleReadme(config, moduleDir);
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Générateur de sitemap PLAT (pas d'imbrication d'index)
3
+ *
4
+ * PRINCIPE:
5
+ * - /sitemap.xml = unique index racine
6
+ * - /sitemap/{module}/{lang}/{type}.xml = sitemaps de contenu (urlset)
7
+ * - PAS d'index intermédiaire /sitemap/{module}/sitemap.xml
8
+ *
9
+ * Conforme aux recommandations Google Search Console :
10
+ * "Un sitemap index ne doit pas référencer un autre sitemap index"
11
+ */
12
+ import type { ModuleBuildConfig } from "../index.js";
13
+ interface SitemapEntry {
14
+ path: string;
15
+ module: string;
16
+ kind: string;
17
+ }
18
+ /**
19
+ * Collecte tous les sitemaps enfants (urlset) de tous les modules
20
+ * Sans créer d'index intermédiaires
21
+ */
22
+ export declare function collectAllContentSitemaps(moduleManifests: Array<{
23
+ config: ModuleBuildConfig;
24
+ manifest: any;
25
+ }>, languages: string[], appDirectory?: string): SitemapEntry[];
26
+ /**
27
+ * Génère le sitemap.xml racine PLAT qui liste directement tous les sitemaps de contenu
28
+ * SANS passer par des index intermédiaires
29
+ */
30
+ export declare function generateFlatGlobalSitemapIndex(appDirectory: string, entries: SitemapEntry[], isDebugMode: boolean): string;
31
+ /**
32
+ * Valide qu'aucun sitemap de contenu ne contient de référence circulaire ou imbrication
33
+ */
34
+ export declare function validateNoNestedIndexes(appDirectory: string, entries: SitemapEntry[], isDebugMode: boolean): {
35
+ valid: boolean;
36
+ errors: string[];
37
+ };
38
+ export {};
39
+ //# sourceMappingURL=sitemap-flat-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sitemap-flat-generator.d.ts","sourceRoot":"","sources":["../../src/scripts/sitemap-flat-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AA6DD;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,eAAe,EAAE,KAAK,CAAC;IACrB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,EAAE,GAAG,CAAC;CACf,CAAC,EACF,SAAS,EAAE,MAAM,EAAE,EACnB,YAAY,CAAC,EAAE,MAAM,GACpB,YAAY,EAAE,CAwFhB;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,YAAY,EAAE,EACvB,WAAW,EAAE,OAAO,UAyDrB;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,YAAY,EAAE,EACvB,WAAW,EAAE,OAAO,GACnB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAyDtC"}
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Générateur de sitemap PLAT (pas d'imbrication d'index)
3
+ *
4
+ * PRINCIPE:
5
+ * - /sitemap.xml = unique index racine
6
+ * - /sitemap/{module}/{lang}/{type}.xml = sitemaps de contenu (urlset)
7
+ * - PAS d'index intermédiaire /sitemap/{module}/sitemap.xml
8
+ *
9
+ * Conforme aux recommandations Google Search Console :
10
+ * "Un sitemap index ne doit pas référencer un autre sitemap index"
11
+ */
12
+ import fs from "node:fs";
13
+ import path from "node:path";
14
+ /**
15
+ * Charge le fichier menu-ignored.ts de l'app si disponible
16
+ */
17
+ function loadMenuIgnored(appDirectory) {
18
+ try {
19
+ const menuIgnoredPath = path.join(appDirectory, "..", "config", "menu-ignored.ts");
20
+ if (!fs.existsSync(menuIgnoredPath)) {
21
+ return [];
22
+ }
23
+ const content = fs.readFileSync(menuIgnoredPath, "utf-8");
24
+ const ignoredPaths = [];
25
+ // Parser simple pour extraire les paths du fichier
26
+ const pathMatches = content.matchAll(/path:\s*["']([^"']+)["']/g);
27
+ for (const match of pathMatches) {
28
+ ignoredPaths.push(match[1]);
29
+ }
30
+ return ignoredPaths;
31
+ }
32
+ catch (error) {
33
+ return [];
34
+ }
35
+ }
36
+ /**
37
+ * Vérifie si un sitemap correspond à une URL ignorée
38
+ */
39
+ function isSitemapIgnored(sitemapPath, ignoredPaths) {
40
+ // Extraire le chemin de base du sitemap (sans /sitemap/{module}/{lang}/)
41
+ // Ex: /sitemap/recipes-pro/fr/static.xml contient les pages comme /fr/...
42
+ for (const ignoredPath of ignoredPaths) {
43
+ // Si le sitemap contient une page ignorée, on l'exclut
44
+ // Note: cette logique peut être affinée selon vos besoins
45
+ if (sitemapPath.includes("static.xml")) {
46
+ // Les sitemaps statiques peuvent contenir des pages ignorées
47
+ // On garde tous les sitemaps statiques pour l'instant
48
+ // car ils peuvent contenir d'autres pages non ignorées
49
+ continue;
50
+ }
51
+ }
52
+ return false;
53
+ }
54
+ /**
55
+ * Collecte tous les sitemaps enfants (urlset) de tous les modules
56
+ * Sans créer d'index intermédiaires
57
+ */
58
+ export function collectAllContentSitemaps(moduleManifests, languages, appDirectory) {
59
+ const entries = [];
60
+ for (const { manifest } of moduleManifests) {
61
+ if (!manifest.enabled)
62
+ continue;
63
+ // Exclure le module auth (routes bloquées pour l'exploration)
64
+ if (manifest.module === "auth")
65
+ continue;
66
+ for (const child of manifest.children || []) {
67
+ if (child.kind === "static") {
68
+ // Générer une entrée par langue
69
+ for (const lang of languages) {
70
+ const childPath = child.path.replace(":lang", lang);
71
+ const fullPath = childPath.startsWith("/")
72
+ ? childPath
73
+ : `/${childPath}`;
74
+ // Ajouter le préfixe du module pour éviter les conflits
75
+ const modulePath = `/sitemap/${manifest.module}${fullPath}`;
76
+ entries.push({
77
+ path: modulePath,
78
+ module: manifest.module,
79
+ kind: child.kind,
80
+ });
81
+ }
82
+ }
83
+ else if (child.kind === "reexport") {
84
+ // Sitemap dynamique - gérer les patterns :lang et :type
85
+ // Vérifier si le path contient d'autres paramètres que :lang
86
+ // (comme :type, :id, etc.) - ces sitemaps ne doivent PAS être dans l'index
87
+ if (child.path.includes(":") &&
88
+ child.path.replace(":lang", "").includes(":")) {
89
+ // Skip les sitemaps avec des paramètres autres que :lang
90
+ // Exemple: :lang/:type/categories.xml contient :type qui n'est pas géré ici
91
+ continue;
92
+ }
93
+ if (child.path.includes(":lang")) {
94
+ // Générer pour chaque langue
95
+ for (const lang of languages) {
96
+ const childPath = child.path.replace(":lang", lang);
97
+ const fullPath = childPath.startsWith("/")
98
+ ? childPath
99
+ : `/${childPath}`;
100
+ // Ajouter le préfixe du module
101
+ const modulePath = `/sitemap/${manifest.module}${fullPath}`;
102
+ entries.push({
103
+ path: modulePath,
104
+ module: manifest.module,
105
+ kind: child.kind,
106
+ });
107
+ }
108
+ }
109
+ else {
110
+ // Pas de paramètre langue - path statique
111
+ const fullPath = child.path.startsWith("/")
112
+ ? child.path
113
+ : `/${child.path}`;
114
+ // Ajouter le préfixe du module
115
+ const modulePath = `/sitemap/${manifest.module}${fullPath}`;
116
+ entries.push({
117
+ path: modulePath,
118
+ module: manifest.module,
119
+ kind: child.kind,
120
+ });
121
+ }
122
+ }
123
+ }
124
+ }
125
+ // Filtrer les sitemaps selon menu-ignored.ts si appDirectory est fourni
126
+ if (appDirectory) {
127
+ const ignoredPaths = loadMenuIgnored(appDirectory);
128
+ if (ignoredPaths.length > 0) {
129
+ // Pour l'instant, on ne filtre pas au niveau du sitemap index
130
+ // car les sitemaps peuvent contenir un mix de pages ignorées et non ignorées
131
+ // Le filtrage devrait idéalement se faire au niveau des handlers de sitemap individuels
132
+ }
133
+ }
134
+ return entries;
135
+ }
136
+ /**
137
+ * Génère le sitemap.xml racine PLAT qui liste directement tous les sitemaps de contenu
138
+ * SANS passer par des index intermédiaires
139
+ */
140
+ export function generateFlatGlobalSitemapIndex(appDirectory, entries, isDebugMode) {
141
+ const globalSitemapDir = path.join(appDirectory, "sitemap.xml");
142
+ const globalSitemapFile = path.join(globalSitemapDir, "route.ts");
143
+ if (!fs.existsSync(globalSitemapDir)) {
144
+ fs.mkdirSync(globalSitemapDir, { recursive: true });
145
+ }
146
+ // Générer les entrées XML
147
+ const sitemapEntries = entries
148
+ .map((entry) => ` <sitemap>
149
+ <loc>\${baseUrl}${entry.path}</loc>
150
+ <lastmod>\${new Date().toISOString()}</lastmod>
151
+ </sitemap>`)
152
+ .join("\n");
153
+ const content = `// GENERATED BY LASTBRAIN MODULE BUILD - FLAT Global Sitemap Index
154
+ // Lists ALL content sitemaps directly (NO nested indexes)
155
+ // Total: ${entries.length} sitemap(s)
156
+ // Modules: ${Array.from(new Set(entries.map((e) => e.module))).join(", ")}
157
+
158
+ export async function GET(): Promise<Response> {
159
+ const baseUrl =
160
+ process.env.NEXT_PUBLIC_SITE_URL ||
161
+ (process.env.VERCEL_URL
162
+ ? \`https://\${process.env.VERCEL_URL}\`
163
+ : "https://example.com");
164
+
165
+ const xml = \`<?xml version="1.0" encoding="UTF-8"?>
166
+ <sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
167
+ ${sitemapEntries}
168
+ </sitemapindex>\`;
169
+
170
+ return new Response(xml, {
171
+ headers: {
172
+ "Content-Type": "application/xml",
173
+ "Cache-Control": "public, max-age=3600, s-maxage=3600",
174
+ },
175
+ });
176
+ }
177
+ `;
178
+ fs.writeFileSync(globalSitemapFile, content);
179
+ if (isDebugMode) {
180
+ console.log(`🌍 Generated FLAT global sitemap index with ${entries.length} content sitemaps`);
181
+ console.log(` Modules: ${Array.from(new Set(entries.map((e) => e.module))).join(", ")}`);
182
+ }
183
+ return globalSitemapFile;
184
+ }
185
+ /**
186
+ * Valide qu'aucun sitemap de contenu ne contient de référence circulaire ou imbrication
187
+ */
188
+ export function validateNoNestedIndexes(appDirectory, entries, isDebugMode) {
189
+ const errors = [];
190
+ // Vérifier qu'aucune entrée ne pointe vers un sitemap.xml (qui serait un index)
191
+ for (const entry of entries) {
192
+ if (entry.path.endsWith("/sitemap.xml")) {
193
+ errors.push(`❌ NESTED INDEX DETECTED: ${entry.path} (module: ${entry.module})`);
194
+ }
195
+ // Vérifier qu'aucune entrée ne pointe vers la racine /sitemap.xml
196
+ if (entry.path === "/sitemap.xml") {
197
+ errors.push(`❌ CIRCULAR REFERENCE: ${entry.path} points to root sitemap (module: ${entry.module})`);
198
+ }
199
+ }
200
+ // Vérifier les fichiers générés pour s'assurer qu'ils sont bien des urlset et pas des sitemapindex
201
+ for (const entry of entries) {
202
+ const segments = entry.path
203
+ .replace(/^\//, "")
204
+ .replace(/\.xml$/, ".xml")
205
+ .split("/");
206
+ const routeDir = path.join(appDirectory, ...segments);
207
+ const routeFile = path.join(routeDir, "route.ts");
208
+ if (fs.existsSync(routeFile)) {
209
+ const content = fs.readFileSync(routeFile, "utf-8");
210
+ // Vérifier si le fichier contient "sitemapindex" (mauvais)
211
+ if (content.includes("<sitemapindex")) {
212
+ errors.push(`❌ FILE IS INDEX NOT URLSET: ${entry.path} contains <sitemapindex> (module: ${entry.module})`);
213
+ }
214
+ // Vérifier s'il contient bien "urlset" (bon)
215
+ if (!content.includes("<urlset") && !content.includes("sitemapindex")) {
216
+ // Peut être un re-export, c'est OK
217
+ if (isDebugMode) {
218
+ console.log(` ℹ️ ${entry.path} is a re-export (OK)`);
219
+ }
220
+ }
221
+ }
222
+ }
223
+ if (errors.length > 0 && isDebugMode) {
224
+ console.error("\n🚨 SITEMAP VALIDATION ERRORS:");
225
+ errors.forEach((err) => console.error(err));
226
+ }
227
+ return {
228
+ valid: errors.length === 0,
229
+ errors,
230
+ };
231
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Nouveau système de sitemaps basé sur les manifests
3
+ * Architecture PLATE (pas d'imbrication) : index global -> sitemaps enfants (urlset)
4
+ * Conforme Google Search Console : pas d'index imbriqués
5
+ */
6
+ import type { ModuleBuildConfig } from "../index.js";
7
+ type SitemapChildKind = "static" | "reexport";
8
+ interface SitemapChild {
9
+ id: string;
10
+ path: string;
11
+ kind: SitemapChildKind;
12
+ handler?: string;
13
+ paging?: {
14
+ pageSize: number;
15
+ };
16
+ }
17
+ interface SitemapManifest {
18
+ module: string;
19
+ enabled: boolean;
20
+ includePublicPagesFromBuildConfig: boolean;
21
+ children: SitemapChild[];
22
+ }
23
+ /**
24
+ * Charge le manifest sitemap d'un module (si existe)
25
+ */
26
+ export declare function loadModuleManifest(moduleConfig: ModuleBuildConfig, projectRequire: NodeRequire, isDebugMode: boolean): Promise<SitemapManifest | null>;
27
+ /**
28
+ * Génère le sitemap.xml racine PLAT - liste directement tous les sitemaps de contenu
29
+ * SUPPRIME les index intermédiaires de modules (pas d'imbrication)
30
+ */
31
+ export declare function generateGlobalSitemapIndex(appDirectory: string, moduleManifests: Array<{
32
+ config: ModuleBuildConfig;
33
+ manifest: SitemapManifest;
34
+ }>, languages: string[], isDebugMode: boolean): void;
35
+ /**
36
+ * SUPPRIMÉE - Les index de modules créent une imbrication non conforme
37
+ * Les sitemaps de contenu sont maintenant listés directement dans /sitemap.xml
38
+ *
39
+ * Ancienne fonction : generateModuleSitemapIndex()
40
+ * Raison suppression : Google Search Console refuse les index imbriqués
41
+ */
42
+ /**
43
+ * Génère un sitemap enfant statique (pages build.config)
44
+ */
45
+ export declare function generateStaticSitemap(appDirectory: string, moduleConfig: ModuleBuildConfig, manifest: SitemapManifest, child: any, lang: string, isDebugMode: boolean): void;
46
+ /**
47
+ * Génère un sitemap enfant dynamique (reexport handler)
48
+ */
49
+ export declare function generateReexportSitemap(appDirectory: string, manifest: SitemapManifest, child: any, lang: string, type?: string, isDebugMode?: boolean): void;
50
+ /**
51
+ * Génère tous les sitemaps pour un module
52
+ */
53
+ export declare function generateModuleSitemaps(appDirectory: string, moduleConfig: ModuleBuildConfig, manifest: SitemapManifest, languages: string[], isDebugMode: boolean): Promise<void>;
54
+ /**
55
+ * Point d'entrée principal du nouveau système de sitemaps
56
+ */
57
+ export declare function generateManifestBasedSitemaps(appDirectory: string, moduleConfigs: ModuleBuildConfig[], languages: string[], projectRequire: NodeRequire, isDebugMode: boolean): Promise<void>;
58
+ export {};
59
+ //# sourceMappingURL=sitemap-manifest-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sitemap-manifest-generator.d.ts","sourceRoot":"","sources":["../../src/scripts/sitemap-manifest-generator.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAsCrD,KAAK,gBAAgB,GAAG,QAAQ,GAAG,UAAU,CAAC;AAE9C,UAAU,YAAY;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,gBAAgB,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE;QACP,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAED,UAAU,eAAe;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,iCAAiC,EAAE,OAAO,CAAC;IAC3C,QAAQ,EAAE,YAAY,EAAE,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,YAAY,EAAE,iBAAiB,EAC/B,cAAc,EAAE,WAAW,EAC3B,WAAW,EAAE,OAAO,GACnB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CA0BjC;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,YAAY,EAAE,MAAM,EACpB,eAAe,EAAE,KAAK,CAAC;IACrB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,EAAE,eAAe,CAAC;CAC3B,CAAC,EACF,SAAS,EAAE,MAAM,EAAE,EACnB,WAAW,EAAE,OAAO,QAwCrB;AAED;;;;;;GAMG;AAGH;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,iBAAiB,EAC/B,QAAQ,EAAE,eAAe,EACzB,KAAK,EAAE,GAAG,EACV,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,OAAO,QAmFrB;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,eAAe,EACzB,KAAK,EAAE,GAAG,EACV,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,MAAM,EACb,WAAW,CAAC,EAAE,OAAO,QAgDtB;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAC1C,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,iBAAiB,EAC/B,QAAQ,EAAE,eAAe,EACzB,SAAS,EAAE,MAAM,EAAE,EACnB,WAAW,EAAE,OAAO,iBA6ErB;AAED;;GAEG;AACH,wBAAsB,6BAA6B,CACjD,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,iBAAiB,EAAE,EAClC,SAAS,EAAE,MAAM,EAAE,EACnB,cAAc,EAAE,WAAW,EAC3B,WAAW,EAAE,OAAO,iBAiErB"}