@lastbrain/app 0.1.39 → 0.1.40

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.
package/dist/cli.js CHANGED
@@ -89,8 +89,13 @@ program
89
89
  program
90
90
  .command("module:build")
91
91
  .description("Build les configurations de modules")
92
- .action(async () => {
92
+ .option("--debug", "Affiche tous les logs détaillés")
93
+ .action(async (options) => {
93
94
  try {
95
+ // Passer le flag debug via process.argv pour que module-build puisse le lire
96
+ if (options.debug && !process.argv.includes("--debug")) {
97
+ process.argv.push("--debug");
98
+ }
94
99
  const { runModuleBuild } = await import("./scripts/module-build.js");
95
100
  await runModuleBuild();
96
101
  }
@@ -723,7 +723,7 @@ export function AppProviders({ children }: { children: ReactNode }) {
723
723
  }
724
724
  async function createConfigFiles(targetDir, force, useHeroUI) {
725
725
  console.log(chalk.yellow("\n⚙️ Création des fichiers de configuration..."));
726
- // middleware.ts - Protection des routes /auth/* et /admin/*
726
+ // middleware.ts - Protection des routes /auth/*, /admin/* et /api/admin/*
727
727
  const middlewarePath = path.join(targetDir, "middleware.ts");
728
728
  if (!fs.existsSync(middlewarePath) || force) {
729
729
  const middleware = `import { type NextRequest, NextResponse } from "next/server";
@@ -731,6 +731,7 @@ import { createMiddlewareClient } from "@lastbrain/core/server";
731
731
 
732
732
  export async function middleware(request: NextRequest) {
733
733
  const { pathname } = request.nextUrl;
734
+ const isApi = pathname.startsWith("/api/");
734
735
 
735
736
  // Protéger les routes /auth/* (espace membre)
736
737
  if (pathname.startsWith("/auth")) {
@@ -740,33 +741,56 @@ export async function middleware(request: NextRequest) {
740
741
  data: { session },
741
742
  } = await supabase.auth.getSession();
742
743
 
743
- // Pas de session → redirection vers /signin
744
+ // Pas de session → nettoyage des cookies + redirection vers /auth/signin
744
745
  if (!session) {
745
- const redirectUrl = new URL("/signin", request.url);
746
+ const redirectUrl = new URL("/auth/signin", request.url);
746
747
  redirectUrl.searchParams.set("redirect", pathname);
747
- return NextResponse.redirect(redirectUrl);
748
+ const res = NextResponse.redirect(redirectUrl);
749
+ res.cookies.delete("sb-access-token");
750
+ res.cookies.delete("sb-refresh-token");
751
+ res.cookies.delete("sb:token");
752
+ res.cookies.delete("sb:refresh-token");
753
+ return res;
748
754
  }
749
755
 
750
756
  return response;
751
757
  } catch (error) {
752
758
  console.error("Middleware auth error:", error);
753
- return NextResponse.redirect(new URL("/signin", request.url));
759
+ const res = NextResponse.redirect(new URL("/auth/signin", request.url));
760
+ res.cookies.delete("sb-access-token");
761
+ res.cookies.delete("sb-refresh-token");
762
+ res.cookies.delete("sb:token");
763
+ res.cookies.delete("sb:refresh-token");
764
+ return res;
754
765
  }
755
766
  }
756
767
 
757
- // Protéger les routes /admin/* (superadmin uniquement)
758
- if (pathname.startsWith("/admin")) {
768
+ // Protéger les routes /admin/* et /api/admin/* (superadmin uniquement)
769
+ if (pathname.startsWith("/admin") || pathname.startsWith("/api/admin")) {
759
770
  try {
760
771
  const { supabase, response } = createMiddlewareClient(request);
761
772
  const {
762
773
  data: { session },
763
774
  } = await supabase.auth.getSession();
764
775
 
765
- // Pas de session → redirection vers /signin
776
+ // Pas de session → 401 JSON pour API, sinon redirection vers /auth/signin avec nettoyage cookies
766
777
  if (!session) {
767
- const redirectUrl = new URL("/signin", request.url);
778
+ if (isApi) {
779
+ const res = NextResponse.json({ error: "Non authentifié" }, { status: 401 });
780
+ res.cookies.delete("sb-access-token");
781
+ res.cookies.delete("sb-refresh-token");
782
+ res.cookies.delete("sb:token");
783
+ res.cookies.delete("sb:refresh-token");
784
+ return res;
785
+ }
786
+ const redirectUrl = new URL("/auth/signin", request.url);
768
787
  redirectUrl.searchParams.set("redirect", pathname);
769
- return NextResponse.redirect(redirectUrl);
788
+ const res = NextResponse.redirect(redirectUrl);
789
+ res.cookies.delete("sb-access-token");
790
+ res.cookies.delete("sb-refresh-token");
791
+ res.cookies.delete("sb:token");
792
+ res.cookies.delete("sb:refresh-token");
793
+ return res;
770
794
  }
771
795
 
772
796
  // Vérifier si l'utilisateur est superadmin
@@ -777,13 +801,24 @@ export async function middleware(request: NextRequest) {
777
801
 
778
802
  if (error || !isSuperAdmin) {
779
803
  console.error("Access denied: not a superadmin", error);
804
+ if (isApi) {
805
+ return NextResponse.json({ error: "Accès refusé" }, { status: 403 });
806
+ }
780
807
  return NextResponse.redirect(new URL("/", request.url));
781
808
  }
782
809
 
783
810
  return response;
784
811
  } catch (error) {
785
812
  console.error("Middleware admin error:", error);
786
- return NextResponse.redirect(new URL("/", request.url));
813
+ if (isApi) {
814
+ return NextResponse.json({ error: "Erreur middleware" }, { status: 500 });
815
+ }
816
+ const res = NextResponse.redirect(new URL("/", request.url));
817
+ res.cookies.delete("sb-access-token");
818
+ res.cookies.delete("sb-refresh-token");
819
+ res.cookies.delete("sb:token");
820
+ res.cookies.delete("sb:refresh-token");
821
+ return res;
787
822
  }
788
823
  }
789
824
 
@@ -804,7 +839,7 @@ export const config = {
804
839
  };
805
840
  `;
806
841
  await fs.writeFile(middlewarePath, middleware);
807
- console.log(chalk.green("✓ middleware.ts créé (protection /auth/* et /admin/*)"));
842
+ console.log(chalk.green("✓ middleware.ts créé (protection /auth/*, /admin/* et /api/admin/*)"));
808
843
  }
809
844
  // next.config.mjs
810
845
  const nextConfigPath = path.join(targetDir, "next.config.mjs");
@@ -1290,6 +1325,13 @@ export const BUCKET_CONFIGS: Record<string, BucketConfig> = {
1290
1325
  return filePath.startsWith(\`\${userId}/\`);
1291
1326
  },
1292
1327
  },
1328
+ recipes: {
1329
+ name: "recipes",
1330
+ isPublic: true,
1331
+ description: "Public recipe images accessible by slug",
1332
+ allowedFileTypes: ["image/jpeg", "image/png", "image/webp", "image/gif"],
1333
+ maxFileSize: 50 * 1024 * 1024, // 50MB
1334
+ },
1293
1335
  // Example for future buckets:
1294
1336
  // public: {
1295
1337
  // name: "public",
@@ -1 +1 @@
1
- {"version":3,"file":"module-add.d.ts","sourceRoot":"","sources":["../../src/scripts/module-add.ts"],"names":[],"mappings":"AAiCA,wBAAsB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,iBA8QpE"}
1
+ {"version":3,"file":"module-add.d.ts","sourceRoot":"","sources":["../../src/scripts/module-add.ts"],"names":[],"mappings":"AAiCA,wBAAsB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,iBA6VpE"}
@@ -146,14 +146,62 @@ export async function addModule(moduleName, targetDir) {
146
146
  else if (migrationAction === "push") {
147
147
  console.log(chalk.yellow("\n⬆️ Application des nouvelles migrations..."));
148
148
  try {
149
- execSync("supabase migration up", {
149
+ // Essayer en capturant la sortie pour diagnostiquer les erreurs fréquentes
150
+ execSync("supabase migration up --local", {
150
151
  cwd: targetDir,
151
- stdio: "inherit",
152
+ stdio: "pipe",
152
153
  });
153
154
  console.log(chalk.green("✓ Migrations appliquées"));
154
155
  }
155
- catch {
156
- console.error(chalk.red("❌ Erreur lors de l'application des migrations"));
156
+ catch (error) {
157
+ const output = String(error?.stderr || error?.stdout || error?.message || "");
158
+ const mismatch = output.includes("Remote migration versions not found in local migrations directory");
159
+ if (mismatch) {
160
+ console.warn(chalk.yellow("⚠️ Historique des migrations désynchronisé."));
161
+ // Extraire TOUTES les dates de migration depuis le message d'erreur
162
+ const repairMatch = output.match(/supabase migration repair --status reverted ([\s\S]+?)\n/);
163
+ let migrationDates = [];
164
+ if (repairMatch && repairMatch[1]) {
165
+ // Extraire toutes les dates de la commande suggérée
166
+ migrationDates = repairMatch[1].trim().split(/\s+/);
167
+ }
168
+ else {
169
+ // Fallback: chercher toutes les dates dans le message
170
+ const allDates = output.match(/20\d{6,14}/g);
171
+ migrationDates = allDates ? [...new Set(allDates)] : [];
172
+ }
173
+ if (migrationDates.length === 0) {
174
+ console.error(chalk.red("❌ Impossible d'extraire les dates de migration"));
175
+ console.log(chalk.gray("\nFaites un reset complet:\n supabase db reset\n"));
176
+ }
177
+ else {
178
+ // Réparation automatique de l'historique
179
+ const datesStr = migrationDates.join(" ");
180
+ console.log(chalk.yellow(`\n🔧 Réparation automatique de l'historique...`));
181
+ console.log(chalk.gray(` Dates: ${datesStr}`));
182
+ try {
183
+ execSync(`supabase migration repair --status reverted ${datesStr} --local`, {
184
+ cwd: targetDir,
185
+ stdio: "inherit",
186
+ });
187
+ console.log(chalk.green("✓ Historique réparé"));
188
+ console.log(chalk.yellow("\n⬆️ Application des nouvelles migrations..."));
189
+ execSync("supabase migration up --local", {
190
+ cwd: targetDir,
191
+ stdio: "inherit",
192
+ });
193
+ console.log(chalk.green("✓ Migrations appliquées"));
194
+ }
195
+ catch (e) {
196
+ console.error(chalk.red("❌ Échec de la réparation automatique"));
197
+ console.log(chalk.gray(`\nVous pouvez essayer manuellement:\n supabase migration repair --status reverted ${datesStr} --local\n supabase migration up --local`));
198
+ console.log(chalk.gray(`\nOu faire un reset complet:\n supabase db reset\n`));
199
+ }
200
+ }
201
+ }
202
+ else {
203
+ console.error(chalk.red("❌ Erreur lors de l'application des migrations"));
204
+ }
157
205
  }
158
206
  }
159
207
  else {
@@ -203,12 +251,12 @@ export async function addModule(moduleName, targetDir) {
203
251
  // 7. Générer automatiquement les routes du module
204
252
  console.log(chalk.yellow("🔧 Génération des routes du module..."));
205
253
  try {
206
- execSync("pnpm build:modules", { cwd: targetDir, stdio: "inherit" });
254
+ execSync("pnpm run build:modules", { cwd: targetDir, stdio: "inherit" });
207
255
  console.log(chalk.green("✓ Routes du module générées"));
208
256
  }
209
257
  catch {
210
258
  console.error(chalk.red("❌ Erreur lors de la génération des routes"));
211
- console.error(chalk.gray("Vous pouvez les générer manuellement avec: pnpm build:modules"));
259
+ console.error(chalk.gray("Vous pouvez les générer manuellement avec: cd apps/<votre-app> && pnpm run build:modules"));
212
260
  }
213
261
  console.log(chalk.gray("\nLe serveur de développement redémarrera automatiquement.\n"));
214
262
  }
@@ -1 +1 @@
1
- {"version":3,"file":"module-build.d.ts","sourceRoot":"","sources":["../../src/scripts/module-build.ts"],"names":[],"mappings":"AAg7BA,wBAAsB,cAAc,kBAoDnC"}
1
+ {"version":3,"file":"module-build.d.ts","sourceRoot":"","sources":["../../src/scripts/module-build.ts"],"names":[],"mappings":"AA69BA,wBAAsB,cAAc,kBA6EnC"}
@@ -8,6 +8,8 @@ const monorepoRoot = projectRoot.includes("/apps/")
8
8
  ? path.resolve(projectRoot, "..", "..")
9
9
  : projectRoot;
10
10
  const appDirectory = path.join(projectRoot, "app");
11
+ // Mode debug activé via --debug
12
+ const isDebugMode = process.argv.includes("--debug");
11
13
  // Créer un require dans le contexte de l'application pour résoudre les modules installés dans l'app
12
14
  const projectRequire = createRequire(path.join(projectRoot, "package.json"));
13
15
  // Charger les modules depuis modules.json
@@ -121,7 +123,9 @@ function buildPage(moduleConfig, page) {
121
123
  const modulePrefix = moduleConfig.moduleName
122
124
  .replace(/^@lastbrain\/module-/, "")
123
125
  .toLowerCase();
124
- console.log(`🔄 Building page for module ${modulePrefix}: ${page.path}`);
126
+ if (isDebugMode) {
127
+ console.log(`🔄 Building page for module ${modulePrefix}: ${page.path}`);
128
+ }
125
129
  // Ajouter le préfixe du module au path pour les sections admin et auth,
126
130
  // MAIS seulement quand la section ne correspond PAS au module lui-même
127
131
  let effectivePath = page.path;
@@ -130,13 +134,17 @@ function buildPage(moduleConfig, page) {
130
134
  // Éviter les doublons si le préfixe est déjà présent
131
135
  if (!page.path.startsWith(`/${modulePrefix}/`)) {
132
136
  effectivePath = `/${modulePrefix}${page.path}`;
133
- console.log(`📂 Added module prefix: ${page.path} -> ${effectivePath}`);
137
+ if (isDebugMode) {
138
+ console.log(`📂 Added module prefix: ${page.path} -> ${effectivePath}`);
139
+ }
134
140
  }
135
- else {
141
+ else if (isDebugMode) {
136
142
  console.log(`✅ Module prefix already present: ${page.path}`);
137
143
  }
138
144
  }
139
- else if (page.section === "auth" && modulePrefix === "auth") {
145
+ else if (page.section === "auth" &&
146
+ modulePrefix === "auth" &&
147
+ isDebugMode) {
140
148
  console.log(`🏠 Auth module in auth section, no prefix needed: ${page.path}`);
141
149
  }
142
150
  const segments = effectivePath.replace(/^\/+/, "").split("/").filter(Boolean);
@@ -215,7 +223,9 @@ export default function ${wrapperName}${hasDynamicParams ? "(props: any)" : "()"
215
223
  `;
216
224
  }
217
225
  fs.writeFileSync(filePath, content);
218
- console.log(`⭐ Generated page: ${filePath}`);
226
+ if (isDebugMode) {
227
+ console.log(`⭐ Generated page: ${filePath}`);
228
+ }
219
229
  const entry = {
220
230
  label: `Module:${moduleConfig.moduleName} ${page.componentExport}`,
221
231
  path: effectivePath,
@@ -238,7 +248,9 @@ export const navigation = ${JSON.stringify(navigation, null, 2)};
238
248
  export const userMenu = ${JSON.stringify(userMenu, null, 2)};
239
249
  `;
240
250
  fs.writeFileSync(navPath, content);
241
- console.log(`🧭 Generated navigation metadata: ${navPath}`);
251
+ if (isDebugMode) {
252
+ console.log(`🧭 Generated navigation metadata: ${navPath}`);
253
+ }
242
254
  }
243
255
  /**
244
256
  * Génère le fichier config/menu.ts à partir des configurations de menu des modules
@@ -305,7 +317,9 @@ export const menuConfig: MenuConfig = {
305
317
  };
306
318
  `;
307
319
  fs.writeFileSync(menuPath, content);
308
- console.log(`🍔 Generated menu configuration: ${menuPath}`);
320
+ if (isDebugMode) {
321
+ console.log(`🍔 Generated menu configuration: ${menuPath}`);
322
+ }
309
323
  }
310
324
  function copyModuleMigrations(moduleConfigs) {
311
325
  const supabaseMigrationsDir = path.join(projectRoot, "supabase", "migrations");
@@ -441,7 +455,9 @@ ${moduleConfigurations.join(",\n")}
441
455
  }
442
456
  `;
443
457
  fs.writeFileSync(docsPagePath, docsContent);
444
- console.log(`📚 Generated docs page: ${docsPagePath}`);
458
+ if (isDebugMode) {
459
+ console.log(`📚 Generated docs page: ${docsPagePath}`);
460
+ }
445
461
  }
446
462
  function buildGroupedApi(apis, routePath) {
447
463
  const segments = routePath.replace(/^\/+/, "").split("/").filter(Boolean);
@@ -468,7 +484,9 @@ function buildGroupedApi(apis, routePath) {
468
484
  const contentWithMarker = `// GENERATED BY LASTBRAIN MODULE BUILD
469
485
  ${content}`;
470
486
  fs.writeFileSync(filePath, contentWithMarker);
471
- console.log(`🔌 Generated API route: ${filePath}`);
487
+ if (isDebugMode) {
488
+ console.log(`🔌 Generated API route: ${filePath}`);
489
+ }
472
490
  }
473
491
  function getModuleDescription(moduleConfig) {
474
492
  // Essayer de déduire la description depuis les pages ou retourner une description par défaut
@@ -523,7 +541,9 @@ function cleanGeneratedFiles() {
523
541
  try {
524
542
  if (!isProtected(itemPath) && fs.readdirSync(itemPath).length === 0) {
525
543
  fs.rmdirSync(itemPath);
526
- console.log(`🗑️ Removed empty directory: ${itemPath}`);
544
+ if (isDebugMode) {
545
+ console.log(`📁 Removed empty directory: ${itemPath}`);
546
+ }
527
547
  }
528
548
  }
529
549
  catch {
@@ -541,14 +561,16 @@ function cleanGeneratedFiles() {
541
561
  (content.includes("export {") &&
542
562
  content.includes('} from "@lastbrain/module-'))) {
543
563
  fs.unlinkSync(itemPath);
544
- console.log(`🗑️ Cleaned generated file: ${itemPath}`);
564
+ if (isDebugMode) {
565
+ console.log(`🗑️ Removed: ${itemPath}`);
566
+ }
545
567
  }
546
568
  }
547
569
  catch {
548
570
  // Ignorer les erreurs de lecture/suppression
549
571
  }
550
572
  }
551
- else {
573
+ else if (isDebugMode) {
552
574
  console.log(`🔒 Protected file skipped: ${itemPath}`);
553
575
  }
554
576
  }
@@ -568,16 +590,22 @@ function cleanGeneratedFiles() {
568
590
  const filePath = path.join(appDirectory, file);
569
591
  if (fs.existsSync(filePath)) {
570
592
  fs.unlinkSync(filePath);
571
- console.log(`🗑️ Cleaned root file: ${filePath}`);
593
+ if (isDebugMode) {
594
+ console.log(`🗑️ Removed: ${filePath}`);
595
+ }
572
596
  }
573
597
  });
574
- console.log("🧹 Cleanup completed");
598
+ if (isDebugMode) {
599
+ console.log("🧹 Cleanup completed");
600
+ }
575
601
  }
576
602
  function generateAppAside() {
577
603
  const targetPath = path.join(appDirectory, "components", "AppAside.tsx");
578
604
  // Ne pas écraser si le fichier existe déjà
579
605
  if (fs.existsSync(targetPath)) {
580
- console.log(`⏭️ AppAside already exists, skipping: ${targetPath}`);
606
+ if (isDebugMode) {
607
+ console.log(`⏭️ AppAside already exists, skipping: ${targetPath}`);
608
+ }
581
609
  return;
582
610
  }
583
611
  const templateContent = `"use client";
@@ -606,7 +634,9 @@ export function AppAside({ className = "", isVisible = true }: AppAsideProps) {
606
634
  try {
607
635
  ensureDirectory(path.dirname(targetPath));
608
636
  fs.writeFileSync(targetPath, templateContent, "utf-8");
609
- console.log(`✅ Generated AppAside component: ${targetPath}`);
637
+ if (isDebugMode) {
638
+ console.log(`✅ Generated AppAside component: ${targetPath}`);
639
+ }
610
640
  }
611
641
  catch (error) {
612
642
  console.error(`❌ Error generating AppAside component: ${error}`);
@@ -633,13 +663,15 @@ export default function SectionLayout({
633
663
  try {
634
664
  ensureDirectory(path.dirname(authLayoutPath));
635
665
  fs.writeFileSync(authLayoutPath, authLayoutContent, "utf-8");
636
- console.log(`✅ Generated auth layout with sidebar: ${authLayoutPath}`);
666
+ if (isDebugMode) {
667
+ console.log(`✅ Generated auth layout with sidebar: ${authLayoutPath}`);
668
+ }
637
669
  }
638
670
  catch (error) {
639
671
  console.error(`❌ Error generating auth layout: ${error}`);
640
672
  }
641
673
  }
642
- else {
674
+ else if (isDebugMode) {
643
675
  console.log(`⏭️ Auth layout already exists, skipping: ${authLayoutPath}`);
644
676
  }
645
677
  // Générer layout admin avec sidebar
@@ -662,13 +694,15 @@ export default function AdminLayout({
662
694
  try {
663
695
  ensureDirectory(path.dirname(adminLayoutPath));
664
696
  fs.writeFileSync(adminLayoutPath, adminLayoutContent, "utf-8");
665
- console.log(`✅ Generated admin layout with sidebar: ${adminLayoutPath}`);
697
+ if (isDebugMode) {
698
+ console.log(`✅ Generated admin layout with sidebar: ${adminLayoutPath}`);
699
+ }
666
700
  }
667
701
  catch (error) {
668
702
  console.error(`❌ Error generating admin layout: ${error}`);
669
703
  }
670
704
  }
671
- else {
705
+ else if (isDebugMode) {
672
706
  console.log(`⏭️ Admin layout already exists, skipping: ${adminLayoutPath}`);
673
707
  }
674
708
  }
@@ -707,15 +741,17 @@ export default realtimeConfig;
707
741
  }
708
742
  // Écrire le fichier TypeScript
709
743
  fs.writeFileSync(outputPath, content);
710
- console.log(`✅ Generated realtime configuration: ${outputPath}`);
711
- console.log(`📊 Modules with realtime: ${realtimeConfigs.length}`);
712
- // Afficher un résumé
713
- realtimeConfigs.forEach((module) => {
714
- console.log(` - ${module.moduleId}: ${module.tables.length} table(s)`);
715
- module.tables.forEach((table) => {
716
- console.log(` • ${table.schema}.${table.table} (${table.event})`);
744
+ if (isDebugMode) {
745
+ console.log(`✅ Generated realtime configuration: ${outputPath}`);
746
+ console.log(`📊 Modules with realtime: ${realtimeConfigs.length}`);
747
+ // Afficher un résumé
748
+ realtimeConfigs.forEach((module) => {
749
+ console.log(` - ${module.moduleId}: ${module.tables.length} table(s)`);
750
+ module.tables.forEach((table) => {
751
+ console.log(` • ${table.schema}.${table.table} (${table.event})`);
752
+ });
717
753
  });
718
- });
754
+ }
719
755
  }
720
756
  catch (error) {
721
757
  console.error("❌ Error generating realtime configuration:", error);
@@ -759,12 +795,14 @@ async function generateUserTabsConfig(moduleConfigs) {
759
795
  }
760
796
  // Écrire le fichier TypeScript
761
797
  fs.writeFileSync(outputPath, appContent);
762
- console.log(`✅ Generated user tabs configuration: ${outputPath}`);
763
- console.log(`📊 User tabs count: ${userTabsConfigs.length}`);
764
- // Afficher un résumé
765
- userTabsConfigs.forEach((tab) => {
766
- console.log(` - ${tab.title} (${tab.moduleName})`);
767
- });
798
+ if (isDebugMode) {
799
+ console.log(`✅ Generated user tabs configuration: ${outputPath}`);
800
+ console.log(`📊 User tabs count: ${userTabsConfigs.length}`);
801
+ // Afficher un résumé
802
+ userTabsConfigs.forEach((tab) => {
803
+ console.log(` - ${tab.title} (${tab.moduleName})`);
804
+ });
805
+ }
768
806
  // Plus de copie vers app/config ni stub core
769
807
  }
770
808
  catch (error) {
@@ -774,16 +812,28 @@ async function generateUserTabsConfig(moduleConfigs) {
774
812
  export async function runModuleBuild() {
775
813
  ensureDirectory(appDirectory);
776
814
  // Nettoyer les fichiers générés précédemment
777
- console.log("🧹 Cleaning previously generated files...");
815
+ if (isDebugMode) {
816
+ console.log("🧹 Cleaning previously generated files...");
817
+ }
778
818
  cleanGeneratedFiles();
779
819
  const moduleConfigs = await loadModuleConfigs();
780
- console.log(`🔍 Loaded ${moduleConfigs.length} module configurations`);
820
+ if (isDebugMode) {
821
+ console.log(`🔍 Loaded ${moduleConfigs.length} module configurations`);
822
+ }
781
823
  // Générer les pages
824
+ if (isDebugMode) {
825
+ console.log("\n📝 Generating pages...");
826
+ }
782
827
  moduleConfigs.forEach((moduleConfig) => {
783
- console.log(`📦 Processing module: ${moduleConfig.moduleName} with ${moduleConfig.pages.length} pages`);
828
+ if (isDebugMode) {
829
+ console.log(`📦 Processing module: ${moduleConfig.moduleName} with ${moduleConfig.pages.length} pages`);
830
+ }
784
831
  moduleConfig.pages.forEach((page) => buildPage(moduleConfig, page));
785
832
  });
786
833
  // Grouper les APIs par chemin pour éviter les écrasements de fichier
834
+ if (isDebugMode) {
835
+ console.log("\n🔌 Generating API routes...");
836
+ }
787
837
  const apisByPath = new Map();
788
838
  moduleConfigs.forEach((moduleConfig) => {
789
839
  moduleConfig.apis.forEach((api) => {
@@ -804,9 +854,22 @@ export async function runModuleBuild() {
804
854
  generateLayouts();
805
855
  copyModuleMigrations(moduleConfigs);
806
856
  // Générer la configuration realtime
807
- console.log("🔄 Generating realtime configuration...");
857
+ if (isDebugMode) {
858
+ console.log("🔄 Generating realtime configuration...");
859
+ }
808
860
  await generateRealtimeConfig(moduleConfigs);
809
861
  // Générer la configuration des user tabs
810
- console.log("📑 Generating user tabs configuration...");
862
+ if (isDebugMode) {
863
+ console.log("📑 Generating user tabs configuration...");
864
+ }
811
865
  await generateUserTabsConfig(moduleConfigs);
866
+ // Message de succès final
867
+ if (!isDebugMode) {
868
+ console.log("\n✅ Module build completed successfully!");
869
+ console.log(`📦 ${moduleConfigs.length} module(s) processed`);
870
+ console.log("💡 Use --debug flag for detailed logs\n");
871
+ }
872
+ else {
873
+ console.log("\n✅ Module build completed (debug mode)\n");
874
+ }
812
875
  }
@@ -1 +1 @@
1
- {"version":3,"file":"module-list.d.ts","sourceRoot":"","sources":["../../src/scripts/module-list.ts"],"names":[],"mappings":"AAQA,wBAAsB,WAAW,CAAC,SAAS,EAAE,MAAM,iBAmClD"}
1
+ {"version":3,"file":"module-list.d.ts","sourceRoot":"","sources":["../../src/scripts/module-list.ts"],"names":[],"mappings":"AAQA,wBAAsB,WAAW,CAAC,SAAS,EAAE,MAAM,iBAwElD"}
@@ -6,26 +6,56 @@ export async function listModules(targetDir) {
6
6
  console.log(chalk.blue("\n📦 Modules disponibles:\n"));
7
7
  // Lire la config des modules installés
8
8
  const modulesConfigPath = path.join(targetDir, ".lastbrain", "modules.json");
9
- let installedModules = [];
9
+ let installedPackages = [];
10
+ let inactivePackages = [];
10
11
  if (fs.existsSync(modulesConfigPath)) {
11
12
  const modulesConfig = await fs.readJson(modulesConfigPath);
12
- installedModules = modulesConfig.modules || [];
13
+ const modules = modulesConfig.modules || [];
14
+ // Supporte deux formats:
15
+ // - Ancien: string[] des noms de packages
16
+ // - Actuel: { package: string; active?: boolean; migrations?: string[] }[]
17
+ if (Array.isArray(modules)) {
18
+ if (modules.length > 0 && typeof modules[0] === "string") {
19
+ installedPackages = modules;
20
+ }
21
+ else {
22
+ const entries = modules;
23
+ installedPackages = entries
24
+ .filter((m) => m && m.package && m.active !== false)
25
+ .map((m) => m.package);
26
+ inactivePackages = entries
27
+ .filter((m) => m && m.package && m.active === false)
28
+ .map((m) => m.package);
29
+ }
30
+ }
13
31
  }
14
32
  // Afficher tous les modules disponibles
15
33
  AVAILABLE_MODULES.forEach((module) => {
16
- const isInstalled = installedModules.includes(module.package);
34
+ const isInstalled = installedPackages.includes(module.package);
35
+ const isInactive = inactivePackages.includes(module.package);
17
36
  const status = isInstalled
18
37
  ? chalk.green("✓ installé")
19
- : chalk.gray(" disponible");
38
+ : isInactive
39
+ ? chalk.yellow("⤳ inactif")
40
+ : chalk.gray(" disponible");
20
41
  console.log(chalk.bold(`${module.emoji} ${module.name.charAt(0).toUpperCase() + module.name.slice(1)}`));
21
42
  console.log(chalk.gray(` Nom: ${module.name}`));
22
43
  console.log(chalk.gray(` Package: ${module.package}`));
23
44
  console.log(chalk.gray(` Description: ${module.description}`));
24
45
  console.log(` Statut: ${status}`);
46
+ // Afficher la commande appropriée selon le statut
47
+ if (isInstalled) {
48
+ console.log(chalk.cyan(` → pnpm lastbrain remove-module ${module.name}`));
49
+ }
50
+ else if (isInactive || !isInstalled) {
51
+ console.log(chalk.cyan(` → pnpm lastbrain add-module ${module.name}`));
52
+ }
25
53
  console.log();
26
54
  });
27
- console.log(chalk.gray("Pour ajouter un module:"));
28
- console.log(chalk.cyan(" pnpm lastbrain add-module <nom>\n"));
29
- console.log(chalk.gray("Pour supprimer un module:"));
30
- console.log(chalk.cyan(" pnpm lastbrain remove-module <nom>\n"));
55
+ console.log(chalk.gray("".repeat(50)));
56
+ console.log(chalk.gray("\nCommandes disponibles:"));
57
+ console.log(chalk.cyan(" pnpm lastbrain add-module <nom> ") +
58
+ chalk.gray("- Ajouter un module"));
59
+ console.log(chalk.cyan(" pnpm lastbrain remove-module <nom> ") +
60
+ chalk.gray("- Supprimer un module\n"));
31
61
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lastbrain/app",
3
- "version": "0.1.39",
3
+ "version": "0.1.40",
4
4
  "description": "Framework modulaire Next.js avec CLI et système de modules",
5
5
  "private": false,
6
6
  "type": "module",
@@ -51,7 +51,7 @@
51
51
  "@tailwindcss/postcss": "^4.1.17",
52
52
  "@types/fs-extra": "^11.0.4",
53
53
  "@types/inquirer": "^9.0.9",
54
- "@types/node": "^20.0.0",
54
+ "@types/node": "^24.10.1",
55
55
  "inquirer": "^13.0.1",
56
56
  "postcss": "^8.4.0",
57
57
  "postcss-cli": "^11.0.0",
package/src/cli.ts CHANGED
@@ -97,8 +97,13 @@ program
97
97
  program
98
98
  .command("module:build")
99
99
  .description("Build les configurations de modules")
100
- .action(async () => {
100
+ .option("--debug", "Affiche tous les logs détaillés")
101
+ .action(async (options) => {
101
102
  try {
103
+ // Passer le flag debug via process.argv pour que module-build puisse le lire
104
+ if (options.debug && !process.argv.includes("--debug")) {
105
+ process.argv.push("--debug");
106
+ }
102
107
  const { runModuleBuild } = await import("./scripts/module-build.js");
103
108
  await runModuleBuild();
104
109
  } catch (error) {
@@ -903,7 +903,7 @@ async function createConfigFiles(
903
903
  ) {
904
904
  console.log(chalk.yellow("\n⚙️ Création des fichiers de configuration..."));
905
905
 
906
- // middleware.ts - Protection des routes /auth/* et /admin/*
906
+ // middleware.ts - Protection des routes /auth/*, /admin/* et /api/admin/*
907
907
  const middlewarePath = path.join(targetDir, "middleware.ts");
908
908
  if (!fs.existsSync(middlewarePath) || force) {
909
909
  const middleware = `import { type NextRequest, NextResponse } from "next/server";
@@ -911,6 +911,7 @@ import { createMiddlewareClient } from "@lastbrain/core/server";
911
911
 
912
912
  export async function middleware(request: NextRequest) {
913
913
  const { pathname } = request.nextUrl;
914
+ const isApi = pathname.startsWith("/api/");
914
915
 
915
916
  // Protéger les routes /auth/* (espace membre)
916
917
  if (pathname.startsWith("/auth")) {
@@ -920,33 +921,56 @@ export async function middleware(request: NextRequest) {
920
921
  data: { session },
921
922
  } = await supabase.auth.getSession();
922
923
 
923
- // Pas de session → redirection vers /signin
924
+ // Pas de session → nettoyage des cookies + redirection vers /auth/signin
924
925
  if (!session) {
925
- const redirectUrl = new URL("/signin", request.url);
926
+ const redirectUrl = new URL("/auth/signin", request.url);
926
927
  redirectUrl.searchParams.set("redirect", pathname);
927
- return NextResponse.redirect(redirectUrl);
928
+ const res = NextResponse.redirect(redirectUrl);
929
+ res.cookies.delete("sb-access-token");
930
+ res.cookies.delete("sb-refresh-token");
931
+ res.cookies.delete("sb:token");
932
+ res.cookies.delete("sb:refresh-token");
933
+ return res;
928
934
  }
929
935
 
930
936
  return response;
931
937
  } catch (error) {
932
938
  console.error("Middleware auth error:", error);
933
- return NextResponse.redirect(new URL("/signin", request.url));
939
+ const res = NextResponse.redirect(new URL("/auth/signin", request.url));
940
+ res.cookies.delete("sb-access-token");
941
+ res.cookies.delete("sb-refresh-token");
942
+ res.cookies.delete("sb:token");
943
+ res.cookies.delete("sb:refresh-token");
944
+ return res;
934
945
  }
935
946
  }
936
947
 
937
- // Protéger les routes /admin/* (superadmin uniquement)
938
- if (pathname.startsWith("/admin")) {
948
+ // Protéger les routes /admin/* et /api/admin/* (superadmin uniquement)
949
+ if (pathname.startsWith("/admin") || pathname.startsWith("/api/admin")) {
939
950
  try {
940
951
  const { supabase, response } = createMiddlewareClient(request);
941
952
  const {
942
953
  data: { session },
943
954
  } = await supabase.auth.getSession();
944
955
 
945
- // Pas de session → redirection vers /signin
956
+ // Pas de session → 401 JSON pour API, sinon redirection vers /auth/signin avec nettoyage cookies
946
957
  if (!session) {
947
- const redirectUrl = new URL("/signin", request.url);
958
+ if (isApi) {
959
+ const res = NextResponse.json({ error: "Non authentifié" }, { status: 401 });
960
+ res.cookies.delete("sb-access-token");
961
+ res.cookies.delete("sb-refresh-token");
962
+ res.cookies.delete("sb:token");
963
+ res.cookies.delete("sb:refresh-token");
964
+ return res;
965
+ }
966
+ const redirectUrl = new URL("/auth/signin", request.url);
948
967
  redirectUrl.searchParams.set("redirect", pathname);
949
- return NextResponse.redirect(redirectUrl);
968
+ const res = NextResponse.redirect(redirectUrl);
969
+ res.cookies.delete("sb-access-token");
970
+ res.cookies.delete("sb-refresh-token");
971
+ res.cookies.delete("sb:token");
972
+ res.cookies.delete("sb:refresh-token");
973
+ return res;
950
974
  }
951
975
 
952
976
  // Vérifier si l'utilisateur est superadmin
@@ -957,13 +981,24 @@ export async function middleware(request: NextRequest) {
957
981
 
958
982
  if (error || !isSuperAdmin) {
959
983
  console.error("Access denied: not a superadmin", error);
984
+ if (isApi) {
985
+ return NextResponse.json({ error: "Accès refusé" }, { status: 403 });
986
+ }
960
987
  return NextResponse.redirect(new URL("/", request.url));
961
988
  }
962
989
 
963
990
  return response;
964
991
  } catch (error) {
965
992
  console.error("Middleware admin error:", error);
966
- return NextResponse.redirect(new URL("/", request.url));
993
+ if (isApi) {
994
+ return NextResponse.json({ error: "Erreur middleware" }, { status: 500 });
995
+ }
996
+ const res = NextResponse.redirect(new URL("/", request.url));
997
+ res.cookies.delete("sb-access-token");
998
+ res.cookies.delete("sb-refresh-token");
999
+ res.cookies.delete("sb:token");
1000
+ res.cookies.delete("sb:refresh-token");
1001
+ return res;
967
1002
  }
968
1003
  }
969
1004
 
@@ -985,7 +1020,9 @@ export const config = {
985
1020
  `;
986
1021
  await fs.writeFile(middlewarePath, middleware);
987
1022
  console.log(
988
- chalk.green("✓ middleware.ts créé (protection /auth/* et /admin/*)"),
1023
+ chalk.green(
1024
+ "✓ middleware.ts créé (protection /auth/*, /admin/* et /api/admin/*)",
1025
+ ),
989
1026
  );
990
1027
  }
991
1028
 
@@ -1561,6 +1598,13 @@ export const BUCKET_CONFIGS: Record<string, BucketConfig> = {
1561
1598
  return filePath.startsWith(\`\${userId}/\`);
1562
1599
  },
1563
1600
  },
1601
+ recipes: {
1602
+ name: "recipes",
1603
+ isPublic: true,
1604
+ description: "Public recipe images accessible by slug",
1605
+ allowedFileTypes: ["image/jpeg", "image/png", "image/webp", "image/gif"],
1606
+ maxFileSize: 50 * 1024 * 1024, // 50MB
1607
+ },
1564
1608
  // Example for future buckets:
1565
1609
  // public: {
1566
1610
  // name: "public",
@@ -205,15 +205,94 @@ export async function addModule(moduleName: string, targetDir: string) {
205
205
  chalk.yellow("\n⬆️ Application des nouvelles migrations..."),
206
206
  );
207
207
  try {
208
- execSync("supabase migration up", {
208
+ // Essayer en capturant la sortie pour diagnostiquer les erreurs fréquentes
209
+ execSync("supabase migration up --local", {
209
210
  cwd: targetDir,
210
- stdio: "inherit",
211
+ stdio: "pipe",
211
212
  });
212
213
  console.log(chalk.green("✓ Migrations appliquées"));
213
- } catch {
214
- console.error(
215
- chalk.red("❌ Erreur lors de l'application des migrations"),
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",
216
220
  );
221
+
222
+ if (mismatch) {
223
+ console.warn(
224
+ chalk.yellow("⚠️ Historique des migrations désynchronisé."),
225
+ );
226
+
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)] : [];
240
+ }
241
+
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 {
292
+ console.error(
293
+ chalk.red("❌ Erreur lors de l'application des migrations"),
294
+ );
295
+ }
217
296
  }
218
297
  } else {
219
298
  console.log(
@@ -287,13 +366,13 @@ export async function addModule(moduleName: string, targetDir: string) {
287
366
  // 7. Générer automatiquement les routes du module
288
367
  console.log(chalk.yellow("🔧 Génération des routes du module..."));
289
368
  try {
290
- execSync("pnpm build:modules", { cwd: targetDir, stdio: "inherit" });
369
+ execSync("pnpm run build:modules", { cwd: targetDir, stdio: "inherit" });
291
370
  console.log(chalk.green("✓ Routes du module générées"));
292
371
  } catch {
293
372
  console.error(chalk.red("❌ Erreur lors de la génération des routes"));
294
373
  console.error(
295
374
  chalk.gray(
296
- "Vous pouvez les générer manuellement avec: pnpm build:modules",
375
+ "Vous pouvez les générer manuellement avec: cd apps/<votre-app> && pnpm run build:modules",
297
376
  ),
298
377
  );
299
378
  }
@@ -19,6 +19,9 @@ const monorepoRoot = projectRoot.includes("/apps/")
19
19
  : projectRoot;
20
20
  const appDirectory = path.join(projectRoot, "app");
21
21
 
22
+ // Mode debug activé via --debug
23
+ const isDebugMode = process.argv.includes("--debug");
24
+
22
25
  // Créer un require dans le contexte de l'application pour résoudre les modules installés dans l'app
23
26
  const projectRequire = createRequire(path.join(projectRoot, "package.json"));
24
27
 
@@ -161,7 +164,9 @@ function buildPage(moduleConfig: ModuleBuildConfig, page: ModulePageConfig) {
161
164
  .replace(/^@lastbrain\/module-/, "")
162
165
  .toLowerCase();
163
166
 
164
- console.log(`🔄 Building page for module ${modulePrefix}: ${page.path}`);
167
+ if (isDebugMode) {
168
+ console.log(`🔄 Building page for module ${modulePrefix}: ${page.path}`);
169
+ }
165
170
 
166
171
  // Ajouter le préfixe du module au path pour les sections admin et auth,
167
172
  // MAIS seulement quand la section ne correspond PAS au module lui-même
@@ -173,11 +178,17 @@ function buildPage(moduleConfig: ModuleBuildConfig, page: ModulePageConfig) {
173
178
  // Éviter les doublons si le préfixe est déjà présent
174
179
  if (!page.path.startsWith(`/${modulePrefix}/`)) {
175
180
  effectivePath = `/${modulePrefix}${page.path}`;
176
- console.log(`📂 Added module prefix: ${page.path} -> ${effectivePath}`);
177
- } else {
181
+ if (isDebugMode) {
182
+ console.log(`📂 Added module prefix: ${page.path} -> ${effectivePath}`);
183
+ }
184
+ } else if (isDebugMode) {
178
185
  console.log(`✅ Module prefix already present: ${page.path}`);
179
186
  }
180
- } else if (page.section === "auth" && modulePrefix === "auth") {
187
+ } else if (
188
+ page.section === "auth" &&
189
+ modulePrefix === "auth" &&
190
+ isDebugMode
191
+ ) {
181
192
  console.log(
182
193
  `🏠 Auth module in auth section, no prefix needed: ${page.path}`,
183
194
  );
@@ -270,7 +281,9 @@ export default function ${wrapperName}${hasDynamicParams ? "(props: any)" : "()"
270
281
  }
271
282
 
272
283
  fs.writeFileSync(filePath, content);
273
- console.log(`⭐ Generated page: ${filePath}`);
284
+ if (isDebugMode) {
285
+ console.log(`⭐ Generated page: ${filePath}`);
286
+ }
274
287
  const entry: MenuEntry = {
275
288
  label: `Module:${moduleConfig.moduleName} ${page.componentExport}`,
276
289
  path: effectivePath,
@@ -294,7 +307,9 @@ export const navigation = ${JSON.stringify(navigation, null, 2)};
294
307
  export const userMenu = ${JSON.stringify(userMenu, null, 2)};
295
308
  `;
296
309
  fs.writeFileSync(navPath, content);
297
- console.log(`🧭 Generated navigation metadata: ${navPath}`);
310
+ if (isDebugMode) {
311
+ console.log(`🧭 Generated navigation metadata: ${navPath}`);
312
+ }
298
313
  }
299
314
 
300
315
  /**
@@ -369,7 +384,9 @@ export const menuConfig: MenuConfig = {
369
384
  `;
370
385
 
371
386
  fs.writeFileSync(menuPath, content);
372
- console.log(`🍔 Generated menu configuration: ${menuPath}`);
387
+ if (isDebugMode) {
388
+ console.log(`🍔 Generated menu configuration: ${menuPath}`);
389
+ }
373
390
  }
374
391
 
375
392
  function copyModuleMigrations(moduleConfigs: ModuleBuildConfig[]) {
@@ -557,7 +574,9 @@ ${moduleConfigurations.join(",\n")}
557
574
  `;
558
575
 
559
576
  fs.writeFileSync(docsPagePath, docsContent);
560
- console.log(`📚 Generated docs page: ${docsPagePath}`);
577
+ if (isDebugMode) {
578
+ console.log(`📚 Generated docs page: ${docsPagePath}`);
579
+ }
561
580
  }
562
581
 
563
582
  function buildGroupedApi(
@@ -595,7 +614,9 @@ function buildGroupedApi(
595
614
  ${content}`;
596
615
 
597
616
  fs.writeFileSync(filePath, contentWithMarker);
598
- console.log(`🔌 Generated API route: ${filePath}`);
617
+ if (isDebugMode) {
618
+ console.log(`🔌 Generated API route: ${filePath}`);
619
+ }
599
620
  }
600
621
 
601
622
  function getModuleDescription(moduleConfig: ModuleBuildConfig): string {
@@ -663,7 +684,9 @@ function cleanGeneratedFiles() {
663
684
  try {
664
685
  if (!isProtected(itemPath) && fs.readdirSync(itemPath).length === 0) {
665
686
  fs.rmdirSync(itemPath);
666
- console.log(`🗑️ Removed empty directory: ${itemPath}`);
687
+ if (isDebugMode) {
688
+ console.log(`📁 Removed empty directory: ${itemPath}`);
689
+ }
667
690
  }
668
691
  } catch {
669
692
  // Ignorer les erreurs de suppression de fichiers
@@ -681,12 +704,14 @@ function cleanGeneratedFiles() {
681
704
  content.includes('} from "@lastbrain/module-'))
682
705
  ) {
683
706
  fs.unlinkSync(itemPath);
684
- console.log(`🗑️ Cleaned generated file: ${itemPath}`);
707
+ if (isDebugMode) {
708
+ console.log(`🗑️ Removed: ${itemPath}`);
709
+ }
685
710
  }
686
711
  } catch {
687
712
  // Ignorer les erreurs de lecture/suppression
688
713
  }
689
- } else {
714
+ } else if (isDebugMode) {
690
715
  console.log(`🔒 Protected file skipped: ${itemPath}`);
691
716
  }
692
717
  }
@@ -708,11 +733,15 @@ function cleanGeneratedFiles() {
708
733
  const filePath = path.join(appDirectory, file);
709
734
  if (fs.existsSync(filePath)) {
710
735
  fs.unlinkSync(filePath);
711
- console.log(`🗑️ Cleaned root file: ${filePath}`);
736
+ if (isDebugMode) {
737
+ console.log(`🗑️ Removed: ${filePath}`);
738
+ }
712
739
  }
713
740
  });
714
741
 
715
- console.log("🧹 Cleanup completed");
742
+ if (isDebugMode) {
743
+ console.log("🧹 Cleanup completed");
744
+ }
716
745
  }
717
746
 
718
747
  function generateAppAside() {
@@ -720,7 +749,9 @@ function generateAppAside() {
720
749
 
721
750
  // Ne pas écraser si le fichier existe déjà
722
751
  if (fs.existsSync(targetPath)) {
723
- console.log(`⏭️ AppAside already exists, skipping: ${targetPath}`);
752
+ if (isDebugMode) {
753
+ console.log(`⏭️ AppAside already exists, skipping: ${targetPath}`);
754
+ }
724
755
  return;
725
756
  }
726
757
 
@@ -751,7 +782,9 @@ export function AppAside({ className = "", isVisible = true }: AppAsideProps) {
751
782
  try {
752
783
  ensureDirectory(path.dirname(targetPath));
753
784
  fs.writeFileSync(targetPath, templateContent, "utf-8");
754
- console.log(`✅ Generated AppAside component: ${targetPath}`);
785
+ if (isDebugMode) {
786
+ console.log(`✅ Generated AppAside component: ${targetPath}`);
787
+ }
755
788
  } catch (error) {
756
789
  console.error(`❌ Error generating AppAside component: ${error}`);
757
790
  }
@@ -779,11 +812,13 @@ export default function SectionLayout({
779
812
  try {
780
813
  ensureDirectory(path.dirname(authLayoutPath));
781
814
  fs.writeFileSync(authLayoutPath, authLayoutContent, "utf-8");
782
- console.log(`✅ Generated auth layout with sidebar: ${authLayoutPath}`);
815
+ if (isDebugMode) {
816
+ console.log(`✅ Generated auth layout with sidebar: ${authLayoutPath}`);
817
+ }
783
818
  } catch (error) {
784
819
  console.error(`❌ Error generating auth layout: ${error}`);
785
820
  }
786
- } else {
821
+ } else if (isDebugMode) {
787
822
  console.log(`⏭️ Auth layout already exists, skipping: ${authLayoutPath}`);
788
823
  }
789
824
 
@@ -808,11 +843,15 @@ export default function AdminLayout({
808
843
  try {
809
844
  ensureDirectory(path.dirname(adminLayoutPath));
810
845
  fs.writeFileSync(adminLayoutPath, adminLayoutContent, "utf-8");
811
- console.log(`✅ Generated admin layout with sidebar: ${adminLayoutPath}`);
846
+ if (isDebugMode) {
847
+ console.log(
848
+ `✅ Generated admin layout with sidebar: ${adminLayoutPath}`,
849
+ );
850
+ }
812
851
  } catch (error) {
813
852
  console.error(`❌ Error generating admin layout: ${error}`);
814
853
  }
815
- } else {
854
+ } else if (isDebugMode) {
816
855
  console.log(`⏭️ Admin layout already exists, skipping: ${adminLayoutPath}`);
817
856
  }
818
857
  }
@@ -860,16 +899,20 @@ export default realtimeConfig;
860
899
  // Écrire le fichier TypeScript
861
900
  fs.writeFileSync(outputPath, content);
862
901
 
863
- console.log(`✅ Generated realtime configuration: ${outputPath}`);
864
- console.log(`📊 Modules with realtime: ${realtimeConfigs.length}`);
902
+ if (isDebugMode) {
903
+ console.log(`✅ Generated realtime configuration: ${outputPath}`);
904
+ console.log(`📊 Modules with realtime: ${realtimeConfigs.length}`);
865
905
 
866
- // Afficher un résumé
867
- realtimeConfigs.forEach((module) => {
868
- console.log(` - ${module.moduleId}: ${module.tables.length} table(s)`);
869
- module.tables.forEach((table) => {
870
- console.log(` • ${table.schema}.${table.table} (${table.event})`);
906
+ // Afficher un résumé
907
+ realtimeConfigs.forEach((module) => {
908
+ console.log(
909
+ ` - ${module.moduleId}: ${module.tables.length} table(s)`,
910
+ );
911
+ module.tables.forEach((table) => {
912
+ console.log(` • ${table.schema}.${table.table} (${table.event})`);
913
+ });
871
914
  });
872
- });
915
+ }
873
916
  } catch (error) {
874
917
  console.error("❌ Error generating realtime configuration:", error);
875
918
  }
@@ -928,13 +971,15 @@ async function generateUserTabsConfig(moduleConfigs: ModuleBuildConfig[]) {
928
971
  // Écrire le fichier TypeScript
929
972
  fs.writeFileSync(outputPath, appContent);
930
973
 
931
- console.log(`✅ Generated user tabs configuration: ${outputPath}`);
932
- console.log(`📊 User tabs count: ${userTabsConfigs.length}`);
974
+ if (isDebugMode) {
975
+ console.log(`✅ Generated user tabs configuration: ${outputPath}`);
976
+ console.log(`📊 User tabs count: ${userTabsConfigs.length}`);
933
977
 
934
- // Afficher un résumé
935
- userTabsConfigs.forEach((tab) => {
936
- console.log(` - ${tab.title} (${tab.moduleName})`);
937
- });
978
+ // Afficher un résumé
979
+ userTabsConfigs.forEach((tab) => {
980
+ console.log(` - ${tab.title} (${tab.moduleName})`);
981
+ });
982
+ }
938
983
 
939
984
  // Plus de copie vers app/config ni stub core
940
985
  } catch (error) {
@@ -946,21 +991,33 @@ export async function runModuleBuild() {
946
991
  ensureDirectory(appDirectory);
947
992
 
948
993
  // Nettoyer les fichiers générés précédemment
949
- console.log("🧹 Cleaning previously generated files...");
994
+ if (isDebugMode) {
995
+ console.log("🧹 Cleaning previously generated files...");
996
+ }
950
997
  cleanGeneratedFiles();
951
998
 
952
999
  const moduleConfigs = await loadModuleConfigs();
953
- console.log(`🔍 Loaded ${moduleConfigs.length} module configurations`);
1000
+ if (isDebugMode) {
1001
+ console.log(`🔍 Loaded ${moduleConfigs.length} module configurations`);
1002
+ }
954
1003
 
955
1004
  // Générer les pages
1005
+ if (isDebugMode) {
1006
+ console.log("\n📝 Generating pages...");
1007
+ }
956
1008
  moduleConfigs.forEach((moduleConfig) => {
957
- console.log(
958
- `📦 Processing module: ${moduleConfig.moduleName} with ${moduleConfig.pages.length} pages`,
959
- );
1009
+ if (isDebugMode) {
1010
+ console.log(
1011
+ `📦 Processing module: ${moduleConfig.moduleName} with ${moduleConfig.pages.length} pages`,
1012
+ );
1013
+ }
960
1014
  moduleConfig.pages.forEach((page) => buildPage(moduleConfig, page));
961
1015
  });
962
1016
 
963
1017
  // Grouper les APIs par chemin pour éviter les écrasements de fichier
1018
+ if (isDebugMode) {
1019
+ console.log("\n🔌 Generating API routes...");
1020
+ }
964
1021
  const apisByPath = new Map<
965
1022
  string,
966
1023
  Array<{ moduleConfig: ModuleBuildConfig; api: ModuleApiConfig }>
@@ -988,10 +1045,23 @@ export async function runModuleBuild() {
988
1045
  copyModuleMigrations(moduleConfigs);
989
1046
 
990
1047
  // Générer la configuration realtime
991
- console.log("🔄 Generating realtime configuration...");
1048
+ if (isDebugMode) {
1049
+ console.log("🔄 Generating realtime configuration...");
1050
+ }
992
1051
  await generateRealtimeConfig(moduleConfigs);
993
1052
 
994
1053
  // Générer la configuration des user tabs
995
- console.log("📑 Generating user tabs configuration...");
1054
+ if (isDebugMode) {
1055
+ console.log("📑 Generating user tabs configuration...");
1056
+ }
996
1057
  await generateUserTabsConfig(moduleConfigs);
1058
+
1059
+ // Message de succès final
1060
+ if (!isDebugMode) {
1061
+ console.log("\n✅ Module build completed successfully!");
1062
+ console.log(`📦 ${moduleConfigs.length} module(s) processed`);
1063
+ console.log("💡 Use --debug flag for detailed logs\n");
1064
+ } else {
1065
+ console.log("\n✅ Module build completed (debug mode)\n");
1066
+ }
997
1067
  }
@@ -11,19 +11,40 @@ export async function listModules(targetDir: string) {
11
11
 
12
12
  // Lire la config des modules installés
13
13
  const modulesConfigPath = path.join(targetDir, ".lastbrain", "modules.json");
14
- let installedModules: string[] = [];
14
+ let installedPackages: string[] = [];
15
+ let inactivePackages: string[] = [];
15
16
 
16
17
  if (fs.existsSync(modulesConfigPath)) {
17
18
  const modulesConfig = await fs.readJson(modulesConfigPath);
18
- installedModules = modulesConfig.modules || [];
19
+ const modules = modulesConfig.modules || [];
20
+
21
+ // Supporte deux formats:
22
+ // - Ancien: string[] des noms de packages
23
+ // - Actuel: { package: string; active?: boolean; migrations?: string[] }[]
24
+ if (Array.isArray(modules)) {
25
+ if (modules.length > 0 && typeof modules[0] === "string") {
26
+ installedPackages = modules as string[];
27
+ } else {
28
+ const entries = modules as Array<{ package: string; active?: boolean }>;
29
+ installedPackages = entries
30
+ .filter((m) => m && m.package && m.active !== false)
31
+ .map((m) => m.package);
32
+ inactivePackages = entries
33
+ .filter((m) => m && m.package && m.active === false)
34
+ .map((m) => m.package);
35
+ }
36
+ }
19
37
  }
20
38
 
21
39
  // Afficher tous les modules disponibles
22
40
  AVAILABLE_MODULES.forEach((module) => {
23
- const isInstalled = installedModules.includes(module.package);
41
+ const isInstalled = installedPackages.includes(module.package);
42
+ const isInactive = inactivePackages.includes(module.package);
24
43
  const status = isInstalled
25
44
  ? chalk.green("✓ installé")
26
- : chalk.gray(" disponible");
45
+ : isInactive
46
+ ? chalk.yellow("⤳ inactif")
47
+ : chalk.gray(" disponible");
27
48
 
28
49
  console.log(
29
50
  chalk.bold(
@@ -34,11 +55,27 @@ export async function listModules(targetDir: string) {
34
55
  console.log(chalk.gray(` Package: ${module.package}`));
35
56
  console.log(chalk.gray(` Description: ${module.description}`));
36
57
  console.log(` Statut: ${status}`);
58
+
59
+ // Afficher la commande appropriée selon le statut
60
+ if (isInstalled) {
61
+ console.log(
62
+ chalk.cyan(` → pnpm lastbrain remove-module ${module.name}`),
63
+ );
64
+ } else if (isInactive || !isInstalled) {
65
+ console.log(chalk.cyan(` → pnpm lastbrain add-module ${module.name}`));
66
+ }
67
+
37
68
  console.log();
38
69
  });
39
70
 
40
- console.log(chalk.gray("Pour ajouter un module:"));
41
- console.log(chalk.cyan(" pnpm lastbrain add-module <nom>\n"));
42
- console.log(chalk.gray("Pour supprimer un module:"));
43
- console.log(chalk.cyan(" pnpm lastbrain remove-module <nom>\n"));
71
+ console.log(chalk.gray("".repeat(50)));
72
+ console.log(chalk.gray("\nCommandes disponibles:"));
73
+ console.log(
74
+ chalk.cyan(" pnpm lastbrain add-module <nom> ") +
75
+ chalk.gray("- Ajouter un module"),
76
+ );
77
+ console.log(
78
+ chalk.cyan(" pnpm lastbrain remove-module <nom> ") +
79
+ chalk.gray("- Supprimer un module\n"),
80
+ );
44
81
  }