@lastbrain/app 0.1.40 → 0.1.43

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.
@@ -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);
@@ -490,75 +490,38 @@ async function createNextStructure(
490
490
  let layoutContent = "";
491
491
 
492
492
  if (useHeroUI) {
493
- // Layout avec HeroUI
493
+ // Layout avec HeroUI - Server Component
494
494
  layoutContent = `// GENERATED BY LASTBRAIN APP-SHELL
495
-
496
- "use client";
495
+ // Server Component pour permettre le SSR des pages enfants
497
496
 
498
497
  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";
498
+ import { ClientLayout } from "../components/ClientLayout";
504
499
  import type { PropsWithChildren } from "react";
505
- import { AppHeader } from "../components/AppHeader";
506
500
 
507
501
  export default function RootLayout({ children }: PropsWithChildren<{}>) {
508
- const router = useRouter();
509
-
510
502
  return (
511
503
  <html lang="fr" suppressHydrationWarning>
512
504
  <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>
505
+ <ClientLayout>{children}</ClientLayout>
528
506
  </body>
529
507
  </html>
530
508
  );
531
509
  }
532
510
  `;
533
511
  } else {
534
- // Layout Tailwind CSS uniquement
512
+ // Layout Tailwind CSS uniquement - Server Component
535
513
  layoutContent = `// GENERATED BY LASTBRAIN APP-SHELL
536
-
537
- "use client";
514
+ // Server Component pour permettre le SSR des pages enfants
538
515
 
539
516
  import "../styles/globals.css";
540
- import { ThemeProvider } from "next-themes";
541
- import { AppProviders } from "../components/AppProviders";
517
+ import { ClientLayout } from "../components/ClientLayout";
542
518
  import type { PropsWithChildren } from "react";
543
- import { AppHeader } from "../components/AppHeader";
544
519
 
545
520
  export default function RootLayout({ children }: PropsWithChildren<{}>) {
546
521
  return (
547
522
  <html lang="fr" suppressHydrationWarning>
548
523
  <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>
524
+ <ClientLayout>{children}</ClientLayout>
562
525
  </body>
563
526
  </html>
564
527
  );
@@ -593,9 +556,16 @@ export default function RootLayout({ children }: PropsWithChildren<{}>) {
593
556
  const homePageContent = `// GENERATED BY LASTBRAIN APP-SHELL
594
557
 
595
558
  import { SimpleHomePage } from "@lastbrain/app";
559
+ import { Footer } from "@lastbrain/ui";
560
+ import { footerConfig } from "../config/footer";
596
561
 
597
562
  export default function RootPage() {
598
- return <SimpleHomePage showAuth={${withAuth}} />;
563
+ return (
564
+ <>
565
+ <SimpleHomePage showAuth={${withAuth}} />
566
+ <Footer config={footerConfig} />
567
+ </>
568
+ );
599
569
  }
600
570
  `;
601
571
  await fs.writeFile(homePagePath, homePageContent);
@@ -643,6 +613,9 @@ export default function NotFound() {
643
613
  await createRoute(appDir, "auth", "auth", force);
644
614
  await createRoute(appDir, "docs", "public", force);
645
615
 
616
+ // Créer le composant ClientLayout (wrapper client avec providers)
617
+ await createClientLayout(targetDir, force, useHeroUI);
618
+
646
619
  // Créer le composant AppHeader
647
620
  await createAppHeader(targetDir, force);
648
621
 
@@ -653,6 +626,92 @@ export default function NotFound() {
653
626
  await createAppProvidersWrapper(targetDir, force);
654
627
  }
655
628
 
629
+ async function createClientLayout(
630
+ targetDir: string,
631
+ force: boolean,
632
+ useHeroUI: boolean,
633
+ ) {
634
+ const componentsDir = path.join(targetDir, "components");
635
+ await fs.ensureDir(componentsDir);
636
+
637
+ const clientLayoutPath = path.join(componentsDir, "ClientLayout.tsx");
638
+
639
+ if (!fs.existsSync(clientLayoutPath) || force) {
640
+ let clientLayoutContent = "";
641
+
642
+ if (useHeroUI) {
643
+ // ClientLayout avec HeroUI
644
+ clientLayoutContent = `"use client";
645
+
646
+ import { HeroUIProvider } from "@heroui/system";
647
+ import { ThemeProvider } from "next-themes";
648
+ import { useRouter } from "next/navigation";
649
+ import { AppProviders } from "./AppProviders";
650
+ import { AppHeader } from "./AppHeader";
651
+ import type { ReactNode } from "react";
652
+
653
+ export function ClientLayout({ children }: { children: ReactNode }) {
654
+ const router = useRouter();
655
+
656
+ return (
657
+ <HeroUIProvider navigate={router.push}>
658
+ <ThemeProvider
659
+ attribute="class"
660
+ defaultTheme="dark"
661
+ enableSystem={false}
662
+ storageKey="lastbrain-theme"
663
+ >
664
+ <AppProviders>
665
+ <AppHeader />
666
+ <div className="min-h-screen text-foreground bg-background">
667
+ {children}
668
+ </div>
669
+ </AppProviders>
670
+ </ThemeProvider>
671
+ </HeroUIProvider>
672
+ );
673
+ }
674
+ `;
675
+ } else {
676
+ // ClientLayout Tailwind CSS uniquement
677
+ clientLayoutContent = `"use client";
678
+
679
+ import { ThemeProvider } from "next-themes";
680
+ import { AppProviders } from "./AppProviders";
681
+ import { AppHeader } from "./AppHeader";
682
+ import type { ReactNode } from "react";
683
+
684
+ export function ClientLayout({ children }: { children: ReactNode }) {
685
+ return (
686
+ <ThemeProvider
687
+ attribute="class"
688
+ defaultTheme="light"
689
+ enableSystem={false}
690
+ storageKey="lastbrain-theme"
691
+ >
692
+ <AppProviders>
693
+ <AppHeader />
694
+ <div className="min-h-screen bg-slate-50 text-slate-900 dark:bg-slate-950 dark:text-white">
695
+ {children}
696
+ </div>
697
+ </AppProviders>
698
+ </ThemeProvider>
699
+ );
700
+ }
701
+ `;
702
+ }
703
+
704
+ await fs.writeFile(clientLayoutPath, clientLayoutContent);
705
+ console.log(chalk.green("✓ components/ClientLayout.tsx créé"));
706
+ } else {
707
+ console.log(
708
+ chalk.gray(
709
+ " components/ClientLayout.tsx existe déjà (utilisez --force pour écraser)",
710
+ ),
711
+ );
712
+ }
713
+ }
714
+
656
715
  async function createRoute(
657
716
  appDir: string,
658
717
  routeName: string,
@@ -700,12 +759,27 @@ export default function AuthLayout({
700
759
  );
701
760
  }`;
702
761
  } else {
703
- // Layout standard pour les autres routes
762
+ // Layout standard pour les autres routes (ex: docs = public)
704
763
  const layoutComponent =
705
764
  layoutType.charAt(0).toUpperCase() + layoutType.slice(1) + "Layout";
706
- layoutContent = `import { ${layoutComponent} } from "@lastbrain/app";
765
+
766
+ if (routeName === "docs") {
767
+ // Layout docs avec footer
768
+ layoutContent = `import { ${layoutComponent} } from "@lastbrain/app";
769
+ import { footerConfig } from "../../config/footer";
770
+
771
+ export default function DocsLayout({
772
+ children,
773
+ }: {
774
+ children: React.ReactNode;
775
+ }) {
776
+ return <${layoutComponent} footerConfig={footerConfig}>{children}</${layoutComponent}>;
777
+ }`;
778
+ } else {
779
+ layoutContent = `import { ${layoutComponent} } from "@lastbrain/app";
707
780
 
708
781
  export default ${layoutComponent};`;
782
+ }
709
783
  }
710
784
 
711
785
  await fs.writeFile(layoutPath, layoutContent);
@@ -900,6 +974,7 @@ async function createConfigFiles(
900
974
  targetDir: string,
901
975
  force: boolean,
902
976
  useHeroUI: boolean,
977
+ projectName?: string,
903
978
  ) {
904
979
  console.log(chalk.yellow("\n⚙️ Création des fichiers de configuration..."));
905
980
 
@@ -1174,6 +1249,26 @@ export const menuConfig: MenuConfig = {
1174
1249
  console.log(chalk.green("✓ config/menu.ts créé"));
1175
1250
  }
1176
1251
 
1252
+ // config/footer.ts
1253
+ const footerConfigPath = path.join(configDir, "footer.ts");
1254
+ if (!fs.existsSync(footerConfigPath) || force) {
1255
+ const footerConfig = `// Auto-generated footer configuration
1256
+ // Run "node ../../scripts/generate-footer-config.js ./apps/[your-app]" to regenerate
1257
+ "use client";
1258
+
1259
+ import type { FooterConfig } from "@lastbrain/ui";
1260
+
1261
+ export const footerConfig: FooterConfig = {
1262
+ companyName: "${projectName}",
1263
+ companyDescription: "Application LastBrain",
1264
+ links: [],
1265
+ social: [],
1266
+ };
1267
+ `;
1268
+ await fs.writeFile(footerConfigPath, footerConfig);
1269
+ console.log(chalk.green("✓ config/footer.ts créé"));
1270
+ }
1271
+
1177
1272
  // Créer les hooks
1178
1273
  await createHooksDirectory(targetDir, force);
1179
1274
  }
@@ -204,94 +204,48 @@ export async function addModule(moduleName: string, targetDir: string) {
204
204
  console.log(
205
205
  chalk.yellow("\n⬆️ Application des nouvelles migrations..."),
206
206
  );
207
- try {
208
- // Essayer en capturant la sortie pour diagnostiquer les erreurs fréquentes
209
- execSync("supabase migration up --local", {
210
- cwd: targetDir,
211
- stdio: "pipe",
212
- });
213
- console.log(chalk.green("✓ Migrations appliquées"));
214
- } catch (error: any) {
215
- const output = String(
216
- error?.stderr || error?.stdout || error?.message || "",
217
- );
218
- const mismatch = output.includes(
219
- "Remote migration versions not found in local migrations directory",
220
- );
221
-
222
- if (mismatch) {
223
- console.warn(
224
- chalk.yellow("⚠️ Historique des migrations désynchronisé."),
225
- );
226
207
 
227
- // Extraire TOUTES les dates de migration depuis le message d'erreur
228
- const repairMatch = output.match(
229
- /supabase migration repair --status reverted ([\s\S]+?)\n/,
230
- );
231
- let migrationDates: string[] = [];
232
-
233
- if (repairMatch && repairMatch[1]) {
234
- // Extraire toutes les dates de la commande suggérée
235
- migrationDates = repairMatch[1].trim().split(/\s+/);
236
- } else {
237
- // Fallback: chercher toutes les dates dans le message
238
- const allDates = output.match(/20\d{6,14}/g);
239
- migrationDates = allDates ? [...new Set(allDates)] : [];
208
+ // Appliquer directement avec psql au lieu de supabase migration up
209
+ const migrationsDir = path.join(targetDir, "supabase", "migrations");
210
+ const migrationFiles = fs
211
+ .readdirSync(migrationsDir)
212
+ .filter((f) => f.endsWith(".sql"))
213
+ .filter((f) => copiedMigrationFiles.includes(f))
214
+ .sort();
215
+
216
+ if (migrationFiles.length === 0) {
217
+ console.log(
218
+ chalk.yellow("⚠️ Aucune nouvelle migration à appliquer"),
219
+ );
220
+ } else {
221
+ try {
222
+ const dbUrl =
223
+ "postgresql://postgres:postgres@127.0.0.1:54322/postgres";
224
+
225
+ for (const file of migrationFiles) {
226
+ const filePath = path.join(migrationsDir, file);
227
+ console.log(chalk.gray(` Exécution de ${file}...`));
228
+
229
+ execSync(`psql "${dbUrl}" -f "${filePath}"`, {
230
+ cwd: targetDir,
231
+ stdio: "pipe",
232
+ });
240
233
  }
241
234
 
242
- if (migrationDates.length === 0) {
243
- console.error(
244
- chalk.red("❌ Impossible d'extraire les dates de migration"),
245
- );
246
- console.log(
247
- chalk.gray("\nFaites un reset complet:\n supabase db reset\n"),
248
- );
249
- } else {
250
- // Réparation automatique de l'historique
251
- const datesStr = migrationDates.join(" ");
252
- console.log(
253
- chalk.yellow(`\n🔧 Réparation automatique de l'historique...`),
254
- );
255
- console.log(chalk.gray(` Dates: ${datesStr}`));
256
-
257
- try {
258
- execSync(
259
- `supabase migration repair --status reverted ${datesStr} --local`,
260
- {
261
- cwd: targetDir,
262
- stdio: "inherit",
263
- },
264
- );
265
- console.log(chalk.green("✓ Historique réparé"));
266
-
267
- console.log(
268
- chalk.yellow("\n⬆️ Application des nouvelles migrations..."),
269
- );
270
- execSync("supabase migration up --local", {
271
- cwd: targetDir,
272
- stdio: "inherit",
273
- });
274
- console.log(chalk.green("✓ Migrations appliquées"));
275
- } catch (e) {
276
- console.error(
277
- chalk.red("❌ Échec de la réparation automatique"),
278
- );
279
- console.log(
280
- chalk.gray(
281
- `\nVous pouvez essayer manuellement:\n supabase migration repair --status reverted ${datesStr} --local\n supabase migration up --local`,
282
- ),
283
- );
284
- console.log(
285
- chalk.gray(
286
- `\nOu faire un reset complet:\n supabase db reset\n`,
287
- ),
288
- );
289
- }
290
- }
291
- } else {
235
+ console.log(
236
+ chalk.green(
237
+ `✓ ${migrationFiles.length} migration(s) appliquée(s)`,
238
+ ),
239
+ );
240
+ } catch (error: any) {
292
241
  console.error(
293
242
  chalk.red("❌ Erreur lors de l'application des migrations"),
294
243
  );
244
+ console.log(
245
+ chalk.gray(
246
+ "\nVous pouvez essayer manuellement:\n supabase db reset\n",
247
+ ),
248
+ );
295
249
  }
296
250
  }
297
251
  } else {