@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
@@ -0,0 +1,476 @@
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
+
7
+ import fs from "node:fs";
8
+ import path from "node:path";
9
+ import type { ModuleBuildConfig } from "../index.js";
10
+ import {
11
+ collectAllContentSitemaps,
12
+ generateFlatGlobalSitemapIndex,
13
+ validateNoNestedIndexes,
14
+ } from "./sitemap-flat-generator.js";
15
+
16
+ /**
17
+ * Charge les paths ignorés depuis menu-ignored.ts de l'app
18
+ */
19
+ function loadMenuIgnoredPaths(appDirectory: string): string[] {
20
+ try {
21
+ const menuIgnoredPath = path.join(
22
+ appDirectory,
23
+ "..",
24
+ "config",
25
+ "menu-ignored.ts"
26
+ );
27
+ if (!fs.existsSync(menuIgnoredPath)) {
28
+ return [];
29
+ }
30
+
31
+ const content = fs.readFileSync(menuIgnoredPath, "utf-8");
32
+ const ignoredPaths: string[] = [];
33
+
34
+ // Parser simple pour extraire les paths du fichier
35
+ const pathMatches = content.matchAll(/path:\s*["']([^"']+)["']/g);
36
+ for (const match of pathMatches) {
37
+ ignoredPaths.push(match[1]);
38
+ }
39
+
40
+ return ignoredPaths;
41
+ } catch (error) {
42
+ return [];
43
+ }
44
+ }
45
+
46
+ // Types dupliqués depuis les modules (pour éviter dépendances circulaires)
47
+ type SitemapChildKind = "static" | "reexport";
48
+
49
+ interface SitemapChild {
50
+ id: string;
51
+ path: string;
52
+ kind: SitemapChildKind;
53
+ handler?: string;
54
+ paging?: {
55
+ pageSize: number;
56
+ };
57
+ }
58
+
59
+ interface SitemapManifest {
60
+ module: string;
61
+ enabled: boolean;
62
+ includePublicPagesFromBuildConfig: boolean;
63
+ children: SitemapChild[];
64
+ }
65
+
66
+ /**
67
+ * Charge le manifest sitemap d'un module (si existe)
68
+ */
69
+ export async function loadModuleManifest(
70
+ moduleConfig: ModuleBuildConfig,
71
+ projectRequire: NodeRequire,
72
+ isDebugMode: boolean
73
+ ): Promise<SitemapManifest | null> {
74
+ try {
75
+ const manifestPath = `${moduleConfig.moduleName}/sitemap/manifest`;
76
+
77
+ if (isDebugMode) {
78
+ console.log(` Trying to load manifest from: ${manifestPath}`);
79
+ }
80
+
81
+ // Utiliser require synchrone plutôt que import dynamique
82
+ const manifestModule = projectRequire(manifestPath);
83
+ const manifest = manifestModule.sitemapManifest;
84
+
85
+ if (manifest && isDebugMode) {
86
+ console.log(` ✅ Loaded manifest for module: ${manifest.module}`);
87
+ }
88
+
89
+ return manifest || null;
90
+ } catch (error) {
91
+ if (isDebugMode) {
92
+ console.log(
93
+ ` ⏭️ No manifest found for ${moduleConfig.moduleName}:`,
94
+ error
95
+ );
96
+ }
97
+ return null;
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Génère le sitemap.xml racine PLAT - liste directement tous les sitemaps de contenu
103
+ * SUPPRIME les index intermédiaires de modules (pas d'imbrication)
104
+ */
105
+ export function generateGlobalSitemapIndex(
106
+ appDirectory: string,
107
+ moduleManifests: Array<{
108
+ config: ModuleBuildConfig;
109
+ manifest: SitemapManifest;
110
+ }>,
111
+ languages: string[],
112
+ isDebugMode: boolean
113
+ ) {
114
+ // Collecter tous les sitemaps de contenu (urlset) directement
115
+ const entries = collectAllContentSitemaps(
116
+ moduleManifests,
117
+ languages,
118
+ appDirectory
119
+ );
120
+
121
+ if (isDebugMode) {
122
+ console.log(`\n📋 Collected ${entries.length} sitemap entries:`);
123
+ entries
124
+ .slice(0, 5)
125
+ .forEach((e) =>
126
+ console.log(` - ${e.path} (module: ${e.module}, kind: ${e.kind})`)
127
+ );
128
+ if (entries.length > 5) {
129
+ console.log(` ... and ${entries.length - 5} more`);
130
+ }
131
+ }
132
+
133
+ // Générer le sitemap.xml racine plat
134
+ generateFlatGlobalSitemapIndex(appDirectory, entries, isDebugMode);
135
+
136
+ // Valider qu'il n'y a pas d'imbrication
137
+ const validation = validateNoNestedIndexes(
138
+ appDirectory,
139
+ entries,
140
+ isDebugMode
141
+ );
142
+
143
+ if (!validation.valid) {
144
+ console.error("\n❌ SITEMAP VALIDATION FAILED:");
145
+ validation.errors.forEach((err) => console.error(err));
146
+ throw new Error("Sitemap validation failed: nested indexes detected");
147
+ }
148
+
149
+ if (isDebugMode) {
150
+ console.log(`✅ Sitemap validation passed: no nested indexes`);
151
+ }
152
+ }
153
+
154
+ /**
155
+ * SUPPRIMÉE - Les index de modules créent une imbrication non conforme
156
+ * Les sitemaps de contenu sont maintenant listés directement dans /sitemap.xml
157
+ *
158
+ * Ancienne fonction : generateModuleSitemapIndex()
159
+ * Raison suppression : Google Search Console refuse les index imbriqués
160
+ */
161
+ // export function generateModuleSitemapIndex() { ... } // DEPRECATED
162
+
163
+ /**
164
+ * Génère un sitemap enfant statique (pages build.config)
165
+ */
166
+ export function generateStaticSitemap(
167
+ appDirectory: string,
168
+ moduleConfig: ModuleBuildConfig,
169
+ manifest: SitemapManifest,
170
+ child: any,
171
+ lang: string,
172
+ isDebugMode: boolean
173
+ ) {
174
+ const childPath = child.path.replace(":lang", lang);
175
+
176
+ // Retirer l'extension .xml du path pour créer un dossier
177
+ const pathWithoutExt = childPath.replace(/\.xml$/, ".xml");
178
+ const pathSegments = pathWithoutExt.split("/").filter(Boolean);
179
+
180
+ const targetDir = path.join(
181
+ appDirectory,
182
+ "sitemap",
183
+ manifest.module,
184
+ ...pathSegments
185
+ );
186
+ const targetFile = path.join(targetDir, "route.ts");
187
+
188
+ if (!fs.existsSync(targetDir)) {
189
+ fs.mkdirSync(targetDir, { recursive: true });
190
+ }
191
+
192
+ // Collecter UNIQUEMENT les pages statiques (sans patterns dynamiques [])
193
+ // Si le manifest spécifie includePublicPagesFromBuildConfig = false, ne pas inclure les pages
194
+ let publicPages: any[] = [];
195
+
196
+ if (manifest.includePublicPagesFromBuildConfig !== false) {
197
+ publicPages = (moduleConfig.pages || [])
198
+ .filter((p) => p.section === "public")
199
+ .filter((p) => !p.path.includes("[") && !p.path.includes("]"));
200
+ }
201
+
202
+ // Charger et exclure les pages dans menu-ignored.ts
203
+ const ignoredPaths = loadMenuIgnoredPaths(appDirectory);
204
+ if (ignoredPaths.length > 0) {
205
+ publicPages = publicPages.filter((p) => {
206
+ // p.path peut être "/blog" ou "/shop"
207
+ // ignoredPaths contient ["/shop", "/prompts", ...]
208
+ // On doit vérifier si p.path match exactement
209
+ return !ignoredPaths.some((ignoredPath) => {
210
+ // Normaliser les chemins (retirer les slashes finaux, etc.)
211
+ const normalizedPagePath = p.path.replace(/\/$/, "");
212
+ const normalizedIgnoredPath = ignoredPath.replace(/\/$/, "");
213
+ return normalizedPagePath === normalizedIgnoredPath;
214
+ });
215
+ });
216
+ }
217
+
218
+ const content = `// GENERATED BY LASTBRAIN MODULE BUILD - Static Pages Sitemap
219
+ // Module: ${manifest.module}
220
+ // Lang: ${lang}
221
+
222
+ export async function GET(): Promise<Response> {
223
+ const baseUrl =
224
+ process.env.NEXT_PUBLIC_SITE_URL ||
225
+ (process.env.VERCEL_URL ? \`https://\${process.env.VERCEL_URL}\` : "https://example.com");
226
+
227
+ const pages: string[] = ${JSON.stringify(publicPages.map((p) => `/${lang}${p.path}`))};
228
+
229
+ const xml = \`<?xml version="1.0" encoding="UTF-8"?>
230
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
231
+ \${pages.map((page: string) => \` <url>
232
+ <loc>\${baseUrl}\${page}</loc>
233
+ <lastmod>\${new Date().toISOString()}</lastmod>
234
+ <changefreq>weekly</changefreq>
235
+ <priority>0.8</priority>
236
+ </url>\`).join("\\n")}
237
+ </urlset>\`;
238
+
239
+ return new Response(xml, {
240
+ headers: {
241
+ "Content-Type": "application/xml",
242
+ "Cache-Control": "public, max-age=3600, s-maxage=3600",
243
+ },
244
+ });
245
+ }
246
+ `;
247
+
248
+ fs.writeFileSync(targetFile, content);
249
+
250
+ if (isDebugMode) {
251
+ console.log(
252
+ `📄 Generated static sitemap: ${targetFile} (${publicPages.length} pages)`
253
+ );
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Génère un sitemap enfant dynamique (reexport handler)
259
+ */
260
+ export function generateReexportSitemap(
261
+ appDirectory: string,
262
+ manifest: SitemapManifest,
263
+ child: any,
264
+ lang: string,
265
+ type?: string,
266
+ isDebugMode?: boolean
267
+ ) {
268
+ let childPath = child.path.replace(":lang", lang);
269
+ if (type) {
270
+ childPath = childPath.replace(":type", type);
271
+ }
272
+
273
+ const segments = childPath.split("/").filter(Boolean);
274
+ const targetDir = path.join(
275
+ appDirectory,
276
+ "sitemap",
277
+ manifest.module,
278
+ ...segments.slice(0, -1)
279
+ );
280
+ const fileName = segments[segments.length - 1];
281
+ const targetFile = path.join(targetDir, fileName, "route.ts");
282
+
283
+ if (!fs.existsSync(path.dirname(targetFile))) {
284
+ fs.mkdirSync(path.dirname(targetFile), { recursive: true });
285
+ }
286
+
287
+ // Créer un wrapper qui passe les params au handler
288
+ const paramsObject: Record<string, string> = { lang };
289
+ if (type) {
290
+ paramsObject.type = type;
291
+ }
292
+
293
+ const content = `// GENERATED BY LASTBRAIN MODULE BUILD - Reexport Sitemap
294
+ // Module: ${manifest.module}
295
+ // Handler: ${child.handler}
296
+ // Params: ${JSON.stringify(paramsObject)}
297
+
298
+ import { GET as handler } from "${child.handler}";
299
+
300
+ export async function GET(request: Request): Promise<Response> {
301
+ // Wrapper pour passer les params au handler
302
+ const context = {
303
+ params: Promise.resolve(${JSON.stringify(paramsObject)})
304
+ };
305
+ return handler(request, context);
306
+ }
307
+ `;
308
+
309
+ fs.writeFileSync(targetFile, content);
310
+
311
+ if (isDebugMode) {
312
+ console.log(`🔗 Generated reexport sitemap: ${targetFile}`);
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Génère tous les sitemaps pour un module
318
+ */
319
+ export async function generateModuleSitemaps(
320
+ appDirectory: string,
321
+ moduleConfig: ModuleBuildConfig,
322
+ manifest: SitemapManifest,
323
+ languages: string[],
324
+ isDebugMode: boolean
325
+ ) {
326
+ // NE PLUS GÉNÉRER D'INDEX INTERMÉDIAIRE
327
+ // Ancien code : generateModuleSitemapIndex() - SUPPRIMÉ pour éviter l'imbrication
328
+ // Le sitemap global /sitemap.xml liste maintenant directement tous les sitemaps enfants
329
+
330
+ // Générer chaque sitemap enfant (sitemaps de contenu = urlset)
331
+ for (const child of manifest.children) {
332
+ if (child.kind === "static") {
333
+ // Générer static.xml pour chaque langue
334
+ for (const lang of languages) {
335
+ generateStaticSitemap(
336
+ appDirectory,
337
+ moduleConfig,
338
+ manifest,
339
+ child,
340
+ lang,
341
+ isDebugMode
342
+ );
343
+ }
344
+ } else if (child.kind === "reexport") {
345
+ // Générer les reexports selon le pattern du path
346
+ if (child.path.includes(":lang") && child.path.includes(":type")) {
347
+ // Ancien système avec :type (déprécié) - Blog: par type et langue
348
+ const contentTypes = ["blog", "tutorial", "faq", "help", "docs"];
349
+ for (const lang of languages) {
350
+ for (const type of contentTypes) {
351
+ generateReexportSitemap(
352
+ appDirectory,
353
+ manifest,
354
+ child,
355
+ lang,
356
+ type,
357
+ isDebugMode
358
+ );
359
+ }
360
+ }
361
+ } else if (child.path.includes(":lang")) {
362
+ // Path avec :lang uniquement
363
+ // Peut contenir un type fixe dans le path comme :lang/blog/categories.xml
364
+ for (const lang of languages) {
365
+ // Extraire le type du path si présent
366
+ const pathSegments = child.path.split("/").filter(Boolean);
367
+ let extractedType: string | undefined;
368
+
369
+ // Si le path est comme :lang/blog/categories.xml, extraire "blog"
370
+ if (pathSegments.length >= 2 && pathSegments[0] === ":lang") {
371
+ const potentialType = pathSegments[1];
372
+ // Vérifier si c'est un type de contenu connu
373
+ const contentTypes = ["blog", "tutorial", "faq", "help", "docs"];
374
+ if (contentTypes.includes(potentialType)) {
375
+ extractedType = potentialType;
376
+ }
377
+ }
378
+
379
+ generateReexportSitemap(
380
+ appDirectory,
381
+ manifest,
382
+ child,
383
+ lang,
384
+ extractedType,
385
+ isDebugMode
386
+ );
387
+ }
388
+ } else {
389
+ // Path statique sans params
390
+ generateReexportSitemap(
391
+ appDirectory,
392
+ manifest,
393
+ child,
394
+ "",
395
+ undefined,
396
+ isDebugMode
397
+ );
398
+ }
399
+ }
400
+ }
401
+ }
402
+
403
+ /**
404
+ * Point d'entrée principal du nouveau système de sitemaps
405
+ */
406
+ export async function generateManifestBasedSitemaps(
407
+ appDirectory: string,
408
+ moduleConfigs: ModuleBuildConfig[],
409
+ languages: string[],
410
+ projectRequire: NodeRequire,
411
+ isDebugMode: boolean
412
+ ) {
413
+ const moduleManifests: Array<{
414
+ config: ModuleBuildConfig;
415
+ manifest: SitemapManifest;
416
+ }> = [];
417
+
418
+ if (isDebugMode) {
419
+ console.log(
420
+ `🔍 Checking ${moduleConfigs.length} module(s) for sitemap manifests...`
421
+ );
422
+ }
423
+
424
+ // Charger les manifests de tous les modules
425
+ for (const config of moduleConfigs) {
426
+ const manifest = await loadModuleManifest(
427
+ config,
428
+ projectRequire,
429
+ isDebugMode
430
+ );
431
+ if (manifest && manifest.enabled) {
432
+ moduleManifests.push({ config, manifest });
433
+ }
434
+ }
435
+
436
+ if (moduleManifests.length === 0) {
437
+ if (isDebugMode) {
438
+ console.log(
439
+ "⏭️ No sitemap manifests found, skipping manifest-based generation"
440
+ );
441
+ }
442
+ return;
443
+ }
444
+
445
+ if (isDebugMode) {
446
+ console.log(
447
+ `🗺️ Found ${moduleManifests.length} module(s) with sitemap manifests`
448
+ );
449
+ }
450
+
451
+ // Générer l'index global PLAT (liste directement tous les sitemaps de contenu)
452
+ generateGlobalSitemapIndex(
453
+ appDirectory,
454
+ moduleManifests,
455
+ languages,
456
+ isDebugMode
457
+ );
458
+
459
+ // Générer les sitemaps de chaque module
460
+ for (const { config, manifest } of moduleManifests) {
461
+ if (isDebugMode) {
462
+ console.log(`📦 Generating sitemaps for module: ${manifest.module}`);
463
+ }
464
+ await generateModuleSitemaps(
465
+ appDirectory,
466
+ config,
467
+ manifest,
468
+ languages,
469
+ isDebugMode
470
+ );
471
+ }
472
+
473
+ if (isDebugMode) {
474
+ console.log("✅ Manifest-based sitemaps generated successfully");
475
+ }
476
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Manifest des sitemaps du package app
3
+ * Package désactivé (pas un module avec pages publiques)
4
+ */
5
+ import type {
6
+ SitemapManifest,
7
+ SitemapChild,
8
+ SitemapChildKind,
9
+ } from "@lastbrain/core";
10
+
11
+ export type { SitemapManifest, SitemapChild, SitemapChildKind };
12
+ export const sitemapManifest: SitemapManifest = {
13
+ module: "app",
14
+ enabled: false,
15
+ includePublicPagesFromBuildConfig: false,
16
+ children: [],
17
+ } as const;
@@ -1,5 +1,5 @@
1
+ "use client";
1
2
  // GENERATED BY LASTBRAIN TEMPLATE (Auth Guide)
2
- import React from "react";
3
3
  import { Card, Code, Divider, Chip } from "@lastbrain/ui";
4
4
 
5
5
  export function AuthGuidePage() {