@lastbrain/app 0.1.42 → 0.1.44

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 (55) hide show
  1. package/dist/__tests__/module-registry.test.js +1 -1
  2. package/dist/layouts/AdminLayout.d.ts.map +1 -1
  3. package/dist/layouts/AdminLayout.js +0 -1
  4. package/dist/layouts/AdminLayoutWithSidebar.d.ts +2 -1
  5. package/dist/layouts/AdminLayoutWithSidebar.d.ts.map +1 -1
  6. package/dist/layouts/AdminLayoutWithSidebar.js +10 -4
  7. package/dist/layouts/AuthLayout.d.ts.map +1 -1
  8. package/dist/layouts/AuthLayout.js +0 -1
  9. package/dist/layouts/AuthLayoutWithSidebar.d.ts +2 -1
  10. package/dist/layouts/AuthLayoutWithSidebar.d.ts.map +1 -1
  11. package/dist/layouts/AuthLayoutWithSidebar.js +10 -4
  12. package/dist/layouts/PublicLayout.d.ts +6 -2
  13. package/dist/layouts/PublicLayout.d.ts.map +1 -1
  14. package/dist/layouts/PublicLayout.js +4 -3
  15. package/dist/layouts/PublicLayoutWithSidebar.d.ts +2 -1
  16. package/dist/layouts/PublicLayoutWithSidebar.d.ts.map +1 -1
  17. package/dist/layouts/PublicLayoutWithSidebar.js +10 -4
  18. package/dist/scripts/db-migrations-sync.js +67 -38
  19. package/dist/scripts/dev-sync.js +1 -0
  20. package/dist/scripts/init-app.d.ts.map +1 -1
  21. package/dist/scripts/init-app.js +172 -59
  22. package/dist/scripts/module-build.d.ts.map +1 -1
  23. package/dist/scripts/module-build.js +67 -6
  24. package/dist/scripts/module-create.d.ts.map +1 -1
  25. package/dist/scripts/module-create.js +19 -0
  26. package/dist/scripts/module-delete.d.ts.map +1 -1
  27. package/dist/scripts/module-delete.js +6 -2
  28. package/dist/scripts/module-list.d.ts.map +1 -1
  29. package/dist/scripts/module-list.js +1 -1
  30. package/dist/styles.css +1 -1
  31. package/dist/templates/DefaultDoc.d.ts.map +1 -1
  32. package/dist/templates/DefaultDoc.js +6 -13
  33. package/dist/templates/DocPage.d.ts.map +1 -1
  34. package/dist/templates/DocPage.js +3 -5
  35. package/dist/templates/SimpleDocPage.js +2 -2
  36. package/dist/templates/SimpleHomePage.js +1 -1
  37. package/package.json +7 -2
  38. package/src/__tests__/module-registry.test.ts +1 -1
  39. package/src/layouts/AdminLayout.tsx +0 -2
  40. package/src/layouts/AdminLayoutWithSidebar.tsx +13 -6
  41. package/src/layouts/AuthLayout.tsx +0 -2
  42. package/src/layouts/AuthLayoutWithSidebar.tsx +13 -6
  43. package/src/layouts/PublicLayout.tsx +14 -2
  44. package/src/layouts/PublicLayoutWithSidebar.tsx +13 -6
  45. package/src/scripts/db-migrations-sync.ts +86 -57
  46. package/src/scripts/dev-sync.ts +1 -0
  47. package/src/scripts/init-app.ts +191 -58
  48. package/src/scripts/module-build.ts +77 -6
  49. package/src/scripts/module-create.ts +25 -2
  50. package/src/scripts/module-delete.ts +8 -6
  51. package/src/scripts/module-list.ts +1 -4
  52. package/src/templates/DefaultDoc.tsx +346 -455
  53. package/src/templates/DocPage.tsx +40 -22
  54. package/src/templates/SimpleDocPage.tsx +2 -2
  55. package/src/templates/SimpleHomePage.tsx +3 -3
@@ -85,7 +85,7 @@ export async function initApp(options: InitAppOptions) {
85
85
  await createNextStructure(targetDir, force, useHeroUI, withAuth);
86
86
 
87
87
  // 4. Créer les fichiers de configuration
88
- await createConfigFiles(targetDir, force, useHeroUI);
88
+ await createConfigFiles(targetDir, force, useHeroUI, projectName);
89
89
 
90
90
  // 5. Créer le système de proxy storage
91
91
  await createStorageProxy(targetDir, force);
@@ -138,6 +138,21 @@ export async function initApp(options: InitAppOptions) {
138
138
  execSync("pnpm build:modules", { cwd: targetDir, stdio: "inherit" });
139
139
  console.log(chalk.green("\n✓ Routes des modules générées\n"));
140
140
 
141
+ console.log(
142
+ chalk.yellow("📜 Synchronisation des migrations des modules...\n"),
143
+ );
144
+ try {
145
+ execSync("pnpm db:migrations:sync", {
146
+ cwd: targetDir,
147
+ stdio: "inherit",
148
+ });
149
+ console.log(chalk.green("\n✓ Migrations synchronisées\n"));
150
+ } catch (error) {
151
+ console.log(
152
+ chalk.yellow("\n⚠️ Erreur de synchronisation des migrations\n"),
153
+ );
154
+ }
155
+
141
156
  console.log(chalk.yellow("🗄️ Initialisation de la base de données...\n"));
142
157
  try {
143
158
  execSync("pnpm db:init", { cwd: targetDir, stdio: "inherit" });
@@ -195,6 +210,7 @@ export async function initApp(options: InitAppOptions) {
195
210
  console.log(chalk.white(` cd ${relativePath}`));
196
211
  console.log(chalk.white(" pnpm install"));
197
212
  console.log(chalk.white(" pnpm build:modules"));
213
+ console.log(chalk.white(" pnpm db:migrations:sync"));
198
214
  console.log(chalk.white(" pnpm db:init"));
199
215
  console.log(chalk.white(" pnpm dev\n"));
200
216
  }
@@ -206,11 +222,12 @@ export async function initApp(options: InitAppOptions) {
206
222
  chalk.white(" 3. pnpm build:modules (générer les routes des modules)"),
207
223
  );
208
224
  console.log(
209
- chalk.white(" 4. pnpm db:init (initialiser la base de données)"),
225
+ chalk.white(" 4. pnpm db:migrations:sync (synchroniser les migrations)"),
226
+ );
227
+ console.log(
228
+ chalk.white(" 5. pnpm db:init (initialiser la base de données)"),
210
229
  );
211
- console.log(chalk.white(" 5. pnpm dev (lancer le serveur)"));
212
- console.log(chalk.white(" 4. pnpm db:init (initialiser Supabase)"));
213
- console.log(chalk.white(" 5. pnpm dev (démarrer le serveur)\n"));
230
+ console.log(chalk.white(" 6. pnpm dev (lancer le serveur)\n"));
214
231
 
215
232
  console.log(chalk.gray("Prérequis pour Supabase :"));
216
233
  console.log(chalk.white(" - Docker Desktop installé et lancé"));
@@ -490,75 +507,38 @@ async function createNextStructure(
490
507
  let layoutContent = "";
491
508
 
492
509
  if (useHeroUI) {
493
- // Layout avec HeroUI
510
+ // Layout avec HeroUI - Server Component
494
511
  layoutContent = `// GENERATED BY LASTBRAIN APP-SHELL
495
-
496
- "use client";
512
+ // Server Component pour permettre le SSR des pages enfants
497
513
 
498
514
  import "../styles/globals.css";
499
- import { HeroUIProvider } from "@heroui/system";
500
-
501
- import { ThemeProvider } from "next-themes";
502
- import { useRouter } from "next/navigation";
503
- import { AppProviders } from "../components/AppProviders";
515
+ import { ClientLayout } from "../components/ClientLayout";
504
516
  import type { PropsWithChildren } from "react";
505
- import { AppHeader } from "../components/AppHeader";
506
517
 
507
518
  export default function RootLayout({ children }: PropsWithChildren<{}>) {
508
- const router = useRouter();
509
-
510
519
  return (
511
520
  <html lang="fr" suppressHydrationWarning>
512
521
  <body className="min-h-screen">
513
- <HeroUIProvider navigate={router.push}>
514
- <ThemeProvider
515
- attribute="class"
516
- defaultTheme="dark"
517
- enableSystem={false}
518
- storageKey="lastbrain-theme"
519
- >
520
- <AppProviders>
521
- <AppHeader />
522
- <div className="min-h-screen text-foreground bg-background">
523
- {children}
524
- </div>
525
- </AppProviders>
526
- </ThemeProvider>
527
- </HeroUIProvider>
522
+ <ClientLayout>{children}</ClientLayout>
528
523
  </body>
529
524
  </html>
530
525
  );
531
526
  }
532
527
  `;
533
528
  } else {
534
- // Layout Tailwind CSS uniquement
529
+ // Layout Tailwind CSS uniquement - Server Component
535
530
  layoutContent = `// GENERATED BY LASTBRAIN APP-SHELL
536
-
537
- "use client";
531
+ // Server Component pour permettre le SSR des pages enfants
538
532
 
539
533
  import "../styles/globals.css";
540
- import { ThemeProvider } from "next-themes";
541
- import { AppProviders } from "../components/AppProviders";
534
+ import { ClientLayout } from "../components/ClientLayout";
542
535
  import type { PropsWithChildren } from "react";
543
- import { AppHeader } from "../components/AppHeader";
544
536
 
545
537
  export default function RootLayout({ children }: PropsWithChildren<{}>) {
546
538
  return (
547
539
  <html lang="fr" suppressHydrationWarning>
548
540
  <body className="min-h-screen">
549
- <ThemeProvider
550
- attribute="class"
551
- defaultTheme="light"
552
- enableSystem={false}
553
- storageKey="lastbrain-theme"
554
- >
555
- <AppProviders>
556
- <AppHeader />
557
- <div className="min-h-screen bg-slate-50 text-slate-900 dark:bg-slate-950 dark:text-white">
558
- {children}
559
- </div>
560
- </AppProviders>
561
- </ThemeProvider>
541
+ <ClientLayout>{children}</ClientLayout>
562
542
  </body>
563
543
  </html>
564
544
  );
@@ -593,9 +573,16 @@ export default function RootLayout({ children }: PropsWithChildren<{}>) {
593
573
  const homePageContent = `// GENERATED BY LASTBRAIN APP-SHELL
594
574
 
595
575
  import { SimpleHomePage } from "@lastbrain/app";
576
+ import { Footer } from "@lastbrain/ui";
577
+ import { footerConfig } from "../config/footer";
596
578
 
597
579
  export default function RootPage() {
598
- return <SimpleHomePage showAuth={${withAuth}} />;
580
+ return (
581
+ <>
582
+ <SimpleHomePage showAuth={${withAuth}} />
583
+ <Footer config={footerConfig} />
584
+ </>
585
+ );
599
586
  }
600
587
  `;
601
588
  await fs.writeFile(homePagePath, homePageContent);
@@ -643,6 +630,9 @@ export default function NotFound() {
643
630
  await createRoute(appDir, "auth", "auth", force);
644
631
  await createRoute(appDir, "docs", "public", force);
645
632
 
633
+ // Créer le composant ClientLayout (wrapper client avec providers)
634
+ await createClientLayout(targetDir, force, useHeroUI);
635
+
646
636
  // Créer le composant AppHeader
647
637
  await createAppHeader(targetDir, force);
648
638
 
@@ -653,6 +643,92 @@ export default function NotFound() {
653
643
  await createAppProvidersWrapper(targetDir, force);
654
644
  }
655
645
 
646
+ async function createClientLayout(
647
+ targetDir: string,
648
+ force: boolean,
649
+ useHeroUI: boolean,
650
+ ) {
651
+ const componentsDir = path.join(targetDir, "components");
652
+ await fs.ensureDir(componentsDir);
653
+
654
+ const clientLayoutPath = path.join(componentsDir, "ClientLayout.tsx");
655
+
656
+ if (!fs.existsSync(clientLayoutPath) || force) {
657
+ let clientLayoutContent = "";
658
+
659
+ if (useHeroUI) {
660
+ // ClientLayout avec HeroUI
661
+ clientLayoutContent = `"use client";
662
+
663
+ import { HeroUIProvider } from "@heroui/system";
664
+ import { ThemeProvider } from "next-themes";
665
+ import { useRouter } from "next/navigation";
666
+ import { AppProviders } from "./AppProviders";
667
+ import { AppHeader } from "./AppHeader";
668
+ import type { ReactNode } from "react";
669
+
670
+ export function ClientLayout({ children }: { children: ReactNode }) {
671
+ const router = useRouter();
672
+
673
+ return (
674
+ <HeroUIProvider navigate={router.push}>
675
+ <ThemeProvider
676
+ attribute="class"
677
+ defaultTheme="dark"
678
+ enableSystem={false}
679
+ storageKey="lastbrain-theme"
680
+ >
681
+ <AppProviders>
682
+ <AppHeader />
683
+ <div className="min-h-screen text-foreground bg-background">
684
+ {children}
685
+ </div>
686
+ </AppProviders>
687
+ </ThemeProvider>
688
+ </HeroUIProvider>
689
+ );
690
+ }
691
+ `;
692
+ } else {
693
+ // ClientLayout Tailwind CSS uniquement
694
+ clientLayoutContent = `"use client";
695
+
696
+ import { ThemeProvider } from "next-themes";
697
+ import { AppProviders } from "./AppProviders";
698
+ import { AppHeader } from "./AppHeader";
699
+ import type { ReactNode } from "react";
700
+
701
+ export function ClientLayout({ children }: { children: ReactNode }) {
702
+ return (
703
+ <ThemeProvider
704
+ attribute="class"
705
+ defaultTheme="light"
706
+ enableSystem={false}
707
+ storageKey="lastbrain-theme"
708
+ >
709
+ <AppProviders>
710
+ <AppHeader />
711
+ <div className="min-h-screen bg-slate-50 text-slate-900 dark:bg-slate-950 dark:text-white">
712
+ {children}
713
+ </div>
714
+ </AppProviders>
715
+ </ThemeProvider>
716
+ );
717
+ }
718
+ `;
719
+ }
720
+
721
+ await fs.writeFile(clientLayoutPath, clientLayoutContent);
722
+ console.log(chalk.green("✓ components/ClientLayout.tsx créé"));
723
+ } else {
724
+ console.log(
725
+ chalk.gray(
726
+ " components/ClientLayout.tsx existe déjà (utilisez --force pour écraser)",
727
+ ),
728
+ );
729
+ }
730
+ }
731
+
656
732
  async function createRoute(
657
733
  appDir: string,
658
734
  routeName: string,
@@ -700,12 +776,27 @@ export default function AuthLayout({
700
776
  );
701
777
  }`;
702
778
  } else {
703
- // Layout standard pour les autres routes
779
+ // Layout standard pour les autres routes (ex: docs = public)
704
780
  const layoutComponent =
705
781
  layoutType.charAt(0).toUpperCase() + layoutType.slice(1) + "Layout";
706
- layoutContent = `import { ${layoutComponent} } from "@lastbrain/app";
782
+
783
+ if (routeName === "docs") {
784
+ // Layout docs avec footer
785
+ layoutContent = `import { ${layoutComponent} } from "@lastbrain/app";
786
+ import { footerConfig } from "../../config/footer";
787
+
788
+ export default function DocsLayout({
789
+ children,
790
+ }: {
791
+ children: React.ReactNode;
792
+ }) {
793
+ return <${layoutComponent} footerConfig={footerConfig}>{children}</${layoutComponent}>;
794
+ }`;
795
+ } else {
796
+ layoutContent = `import { ${layoutComponent} } from "@lastbrain/app";
707
797
 
708
798
  export default ${layoutComponent};`;
799
+ }
709
800
  }
710
801
 
711
802
  await fs.writeFile(layoutPath, layoutContent);
@@ -900,6 +991,7 @@ async function createConfigFiles(
900
991
  targetDir: string,
901
992
  force: boolean,
902
993
  useHeroUI: boolean,
994
+ projectName?: string,
903
995
  ) {
904
996
  console.log(chalk.yellow("\n⚙️ Création des fichiers de configuration..."));
905
997
 
@@ -913,6 +1005,24 @@ export async function middleware(request: NextRequest) {
913
1005
  const { pathname } = request.nextUrl;
914
1006
  const isApi = pathname.startsWith("/api/");
915
1007
 
1008
+ // Pages publiques d'authentification (ne pas protéger)
1009
+ const publicAuthPages = [
1010
+ "/signin",
1011
+ "/signup",
1012
+ "/reset-password",
1013
+ "/forgot-password",
1014
+ "/callback",
1015
+ ];
1016
+
1017
+ const isPublicAuthPage = publicAuthPages.some((page) =>
1018
+ pathname.startsWith(page)
1019
+ );
1020
+
1021
+ // Ne pas protéger les pages publiques d'authentification
1022
+ if (isPublicAuthPage) {
1023
+ return NextResponse.next();
1024
+ }
1025
+
916
1026
  // Protéger les routes /auth/* (espace membre)
917
1027
  if (pathname.startsWith("/auth")) {
918
1028
  try {
@@ -921,9 +1031,9 @@ export async function middleware(request: NextRequest) {
921
1031
  data: { session },
922
1032
  } = await supabase.auth.getSession();
923
1033
 
924
- // Pas de session → nettoyage des cookies + redirection vers /auth/signin
1034
+ // Pas de session → nettoyage des cookies + redirection vers /signin
925
1035
  if (!session) {
926
- const redirectUrl = new URL("/auth/signin", request.url);
1036
+ const redirectUrl = new URL("/signin", request.url);
927
1037
  redirectUrl.searchParams.set("redirect", pathname);
928
1038
  const res = NextResponse.redirect(redirectUrl);
929
1039
  res.cookies.delete("sb-access-token");
@@ -936,7 +1046,7 @@ export async function middleware(request: NextRequest) {
936
1046
  return response;
937
1047
  } catch (error) {
938
1048
  console.error("Middleware auth error:", error);
939
- const res = NextResponse.redirect(new URL("/auth/signin", request.url));
1049
+ const res = NextResponse.redirect(new URL("/signin", request.url));
940
1050
  res.cookies.delete("sb-access-token");
941
1051
  res.cookies.delete("sb-refresh-token");
942
1052
  res.cookies.delete("sb:token");
@@ -953,7 +1063,7 @@ export async function middleware(request: NextRequest) {
953
1063
  data: { session },
954
1064
  } = await supabase.auth.getSession();
955
1065
 
956
- // Pas de session → 401 JSON pour API, sinon redirection vers /auth/signin avec nettoyage cookies
1066
+ // Pas de session → 401 JSON pour API, sinon redirection vers /signin avec nettoyage cookies
957
1067
  if (!session) {
958
1068
  if (isApi) {
959
1069
  const res = NextResponse.json({ error: "Non authentifié" }, { status: 401 });
@@ -963,7 +1073,7 @@ export async function middleware(request: NextRequest) {
963
1073
  res.cookies.delete("sb:refresh-token");
964
1074
  return res;
965
1075
  }
966
- const redirectUrl = new URL("/auth/signin", request.url);
1076
+ const redirectUrl = new URL("/signin", request.url);
967
1077
  redirectUrl.searchParams.set("redirect", pathname);
968
1078
  const res = NextResponse.redirect(redirectUrl);
969
1079
  res.cookies.delete("sb-access-token");
@@ -1032,6 +1142,9 @@ export const config = {
1032
1142
  const nextConfig = `/** @type {import('next').NextConfig} */
1033
1143
  const nextConfig = {
1034
1144
  reactStrictMode: true,
1145
+ devIndicators: {
1146
+ position: 'bottom-right',
1147
+ },
1035
1148
  };
1036
1149
 
1037
1150
  export default nextConfig;
@@ -1174,6 +1287,26 @@ export const menuConfig: MenuConfig = {
1174
1287
  console.log(chalk.green("✓ config/menu.ts créé"));
1175
1288
  }
1176
1289
 
1290
+ // config/footer.ts
1291
+ const footerConfigPath = path.join(configDir, "footer.ts");
1292
+ if (!fs.existsSync(footerConfigPath) || force) {
1293
+ const footerConfig = `// Auto-generated footer configuration
1294
+ // Run "node ../../scripts/generate-footer-config.js ./apps/[your-app]" to regenerate
1295
+ "use client";
1296
+
1297
+ import type { FooterConfig } from "@lastbrain/ui";
1298
+
1299
+ export const footerConfig: FooterConfig = {
1300
+ companyName: "${projectName}",
1301
+ companyDescription: "Application LastBrain",
1302
+ links: [],
1303
+ social: [],
1304
+ };
1305
+ `;
1306
+ await fs.writeFile(footerConfigPath, footerConfig);
1307
+ console.log(chalk.green("✓ config/footer.ts créé"));
1308
+ }
1309
+
1177
1310
  // Créer les hooks
1178
1311
  await createHooksDirectory(targetDir, force);
1179
1312
  }
@@ -47,7 +47,11 @@ async function loadModuleConfigs(): Promise<ModuleBuildConfig[]> {
47
47
  const packageName = module.package;
48
48
 
49
49
  try {
50
- const moduleSuffix = packageName.replace("@lastbrain/module-", "");
50
+ // Extraire le suffix du module (la partie après @lastbrain/module- ou @lastbrain-labs/module-)
51
+ const moduleSuffix = packageName
52
+ .replace("@lastbrain/module-", "")
53
+ .replace("@lastbrain-labs/module-", "");
54
+
51
55
  const possibleConfigNames = [
52
56
  `${moduleSuffix}.build.config`,
53
57
  "build.config",
@@ -159,9 +163,10 @@ function toPascalCase(value: string) {
159
163
  }
160
164
 
161
165
  function buildPage(moduleConfig: ModuleBuildConfig, page: ModulePageConfig) {
162
- // Extraire le préfixe du module (ex: @lastbrain/module-auth -> auth)
166
+ // Extraire le préfixe du module (ex: @lastbrain/module-auth -> auth, @lastbrain-labs/module-recipes-pro -> recipes-pro)
163
167
  const modulePrefix = moduleConfig.moduleName
164
168
  .replace(/^@lastbrain\/module-/, "")
169
+ .replace(/^@lastbrain-labs\/module-/, "")
165
170
  .toLowerCase();
166
171
 
167
172
  if (isDebugMode) {
@@ -593,7 +598,14 @@ function generateDocsPage(moduleConfigs: ModuleBuildConfig[]) {
593
598
 
594
599
  allModules.forEach((moduleEntry) => {
595
600
  const moduleName = moduleEntry.package;
596
- const moduleId = moduleName.replace("@lastbrain/module-", "");
601
+ // Extraire le nom du module sans le scope et sans "module-"
602
+ // Ex: @lastbrain/module-auth -> auth
603
+ // Ex: @lastbrain-labs/module-recipes-pro -> recipes
604
+ const moduleId = moduleName
605
+ .replace("@lastbrain-labs/module-", "")
606
+ .replace("@lastbrain/module-", "")
607
+ .replace(/-pro$/, ""); // Retirer le suffix -pro pour avoir le nom de base
608
+
597
609
  const docComponentName = `${toPascalCase(moduleId)}ModuleDoc`;
598
610
 
599
611
  // Trouver la config du module pour obtenir la description
@@ -610,8 +622,8 @@ function generateDocsPage(moduleConfigs: ModuleBuildConfig[]) {
610
622
  }
611
623
 
612
624
  const config = {
613
- id: moduleId,
614
- name: `Module ${moduleId.charAt(0).toUpperCase() + moduleId.slice(1)}`,
625
+ id: moduleName, // Utiliser le nom complet du package comme ID
626
+ name: `Module ${toPascalCase(moduleId)}`,
615
627
  description: description,
616
628
  component: docComponentName,
617
629
  active: moduleEntry.active,
@@ -1241,7 +1253,6 @@ import { NextRequest, NextResponse } from "next/server";
1241
1253
  * GENERATED FILE - DO NOT EDIT MANUALLY
1242
1254
  * Generated at: ${timestamp}
1243
1255
  * Generated from module storage configurations
1244
- *
1245
1256
  * Buckets configurés:
1246
1257
  ${allBuckets.map((b) => ` * - ${b.name} (${b.public ? "public" : "private"})`).join("\n")}
1247
1258
  */
@@ -1350,6 +1361,60 @@ export async function GET(
1350
1361
  }
1351
1362
  }
1352
1363
 
1364
+ async function generateFooterConfig(moduleConfigs: ModuleBuildConfig[]) {
1365
+ try {
1366
+ // Extraire tous les liens footer des modules
1367
+ const allFooterLinks = moduleConfigs
1368
+ .filter((config) => config.footer && config.footer.length > 0)
1369
+ .flatMap((config) => config.footer!);
1370
+
1371
+ if (allFooterLinks.length === 0) {
1372
+ console.log(
1373
+ "⏭️ No footer links found, skipping footer config generation",
1374
+ );
1375
+ return;
1376
+ }
1377
+
1378
+ // Trier les liens par position et order
1379
+ allFooterLinks.sort((a, b) => {
1380
+ const posOrder = { left: 0, center: 1, right: 2 };
1381
+ const posA = posOrder[a.position || "left"] || 0;
1382
+ const posB = posOrder[b.position || "left"] || 0;
1383
+ if (posA !== posB) return posA - posB;
1384
+ return (a.order || 0) - (b.order || 0);
1385
+ });
1386
+
1387
+ const timestamp = new Date().toISOString();
1388
+ const content = `// Auto-generated footer configuration
1389
+ // Generated from module build configs
1390
+ // Generated at: ${timestamp}
1391
+ "use client";
1392
+
1393
+ import type { FooterConfig } from "@lastbrain/ui";
1394
+
1395
+ export const footerConfig: FooterConfig = {
1396
+ companyName: "LastBrain",
1397
+ companyDescription: "Plateforme de développement rapide d'applications",
1398
+ links: ${JSON.stringify(allFooterLinks, null, 2)},
1399
+ social: [],
1400
+ };
1401
+ `;
1402
+
1403
+ const configDir = path.join(projectRoot, "config");
1404
+ ensureDirectory(configDir);
1405
+
1406
+ const footerPath = path.join(configDir, "footer.ts");
1407
+ fs.writeFileSync(footerPath, content);
1408
+
1409
+ if (isDebugMode) {
1410
+ console.log(`✅ Generated footer config: ${footerPath}`);
1411
+ console.log(` - ${allFooterLinks.length} footer link(s)`);
1412
+ }
1413
+ } catch (error) {
1414
+ console.error("❌ Error generating footer config:", error);
1415
+ }
1416
+ }
1417
+
1353
1418
  export async function runModuleBuild() {
1354
1419
  ensureDirectory(appDirectory);
1355
1420
 
@@ -1431,6 +1496,12 @@ export async function runModuleBuild() {
1431
1496
  }
1432
1497
  await generateStorageProxyApi(moduleConfigs);
1433
1498
 
1499
+ // Générer la configuration footer
1500
+ if (isDebugMode) {
1501
+ console.log("🦶 Generating footer configuration...");
1502
+ }
1503
+ await generateFooterConfig(moduleConfigs);
1504
+
1434
1505
  // Message de succès final
1435
1506
  if (!isDebugMode) {
1436
1507
  console.log("\n✅ Module build completed successfully!");
@@ -1028,12 +1028,21 @@ ${pagesSection}${apisSection}${tablesSection}${installSection}${usageSection}${d
1028
1028
  /**
1029
1029
  * Generate README.md for the module
1030
1030
  */
1031
- async function generateModuleReadme(config: ModuleConfig, moduleDir: string) {
1031
+ async function generateModuleReadme(
1032
+ config: ModuleConfig & { description?: string },
1033
+ moduleDir: string,
1034
+ ) {
1032
1035
  const moduleNameClean = config.slug.replace("module-", "");
1033
1036
 
1034
1037
  let md = `# 📦 Module ${moduleNameClean}\n\n`;
1035
1038
  md += `> ${config.moduleName}\n\n`;
1036
1039
 
1040
+ // Description section
1041
+ if (config.description) {
1042
+ md += `## 📝 Description\n\n`;
1043
+ md += `${config.description}\n\n`;
1044
+ }
1045
+
1037
1046
  // Information section
1038
1047
  md += `## 📋 Informations\n\n`;
1039
1048
  md += `- **Nom du package**: \`${config.moduleName}\`\n`;
@@ -1533,6 +1542,18 @@ export async function createModule() {
1533
1542
  },
1534
1543
  filter: (input: string) => input.trim().toLowerCase(),
1535
1544
  },
1545
+ {
1546
+ type: "input",
1547
+ name: "description",
1548
+ message: "Description du module (une ligne):",
1549
+ default: "Module LastBrain",
1550
+ validate: (input) => {
1551
+ if (!input || input.trim() === "") {
1552
+ return "La description est requise";
1553
+ }
1554
+ return true;
1555
+ },
1556
+ },
1536
1557
  {
1537
1558
  type: "input",
1538
1559
  name: "pagesPublic",
@@ -1565,6 +1586,7 @@ export async function createModule() {
1565
1586
  // Construire la configuration du module
1566
1587
  const slug = `module-${answers.slug}`;
1567
1588
  const moduleName = `@lastbrain/${slug}`;
1589
+ const description = answers.description;
1568
1590
 
1569
1591
  const pages: PageConfig[] = [];
1570
1592
 
@@ -1664,11 +1686,12 @@ export async function createModule() {
1664
1686
  });
1665
1687
  }
1666
1688
 
1667
- const config: ModuleConfig = {
1689
+ const config: ModuleConfig & { description?: string } = {
1668
1690
  slug,
1669
1691
  moduleName,
1670
1692
  pages,
1671
1693
  tables,
1694
+ description,
1672
1695
  };
1673
1696
 
1674
1697
  // Trouver le répertoire racine du workspace (chercher pnpm-workspace.yaml)
@@ -38,7 +38,7 @@ export async function deleteModule() {
38
38
 
39
39
  const answers = await inquirer.prompt([
40
40
  {
41
- type: "list",
41
+ type: "select",
42
42
  name: "moduleName",
43
43
  message: "Quel module voulez-vous supprimer du monorepo ?",
44
44
  choices: AVAILABLE_MODULES.map((m) => ({
@@ -73,11 +73,13 @@ export async function deleteModule() {
73
73
  );
74
74
 
75
75
  // 1. Supprimer le répertoire du module
76
- const moduleDir = path.join(
77
- rootDir,
78
- "packages",
79
- moduleMeta.package.replace("@lastbrain/", ""),
80
- );
76
+ // Extraire le nom du package correctement (gérer @lastbrain et @lastbrain-labs)
77
+ const packageName = moduleMeta.package
78
+ .replace("@lastbrain-labs/", "")
79
+ .replace("@lastbrain/", "");
80
+
81
+ const moduleDir = path.join(rootDir, "packages", packageName);
82
+
81
83
  if (fs.existsSync(moduleDir)) {
82
84
  console.log(chalk.yellow(`📁 Suppression du répertoire: ${moduleDir}`));
83
85
  await fs.remove(moduleDir);
@@ -1,10 +1,7 @@
1
1
  import fs from "fs-extra";
2
2
  import path from "path";
3
3
  import chalk from "chalk";
4
- import {
5
- AVAILABLE_MODULES,
6
- type ModuleMetadata,
7
- } from "@lastbrain/core/config/modules";
4
+ import { AVAILABLE_MODULES } from "@lastbrain/core/config/modules";
8
5
 
9
6
  export async function listModules(targetDir: string) {
10
7
  console.log(chalk.blue("\n📦 Modules disponibles:\n"));