@lastbrain/app 0.1.25 → 0.1.27

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 (54) hide show
  1. package/dist/app-shell/(public)/page.d.ts.map +1 -1
  2. package/dist/index.d.ts +4 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +4 -0
  5. package/dist/layouts/AdminLayoutWithSidebar.d.ts +8 -0
  6. package/dist/layouts/AdminLayoutWithSidebar.d.ts.map +1 -0
  7. package/dist/layouts/AdminLayoutWithSidebar.js +9 -0
  8. package/dist/layouts/AuthLayoutWithSidebar.d.ts +8 -0
  9. package/dist/layouts/AuthLayoutWithSidebar.d.ts.map +1 -0
  10. package/dist/layouts/AuthLayoutWithSidebar.js +9 -0
  11. package/dist/scripts/db-init.js +2 -2
  12. package/dist/scripts/dev-sync.js +23 -12
  13. package/dist/scripts/init-app.d.ts.map +1 -1
  14. package/dist/scripts/init-app.js +114 -12
  15. package/dist/scripts/module-add.d.ts.map +1 -1
  16. package/dist/scripts/module-add.js +19 -6
  17. package/dist/scripts/module-build.d.ts.map +1 -1
  18. package/dist/scripts/module-build.js +288 -30
  19. package/dist/scripts/module-create.d.ts.map +1 -1
  20. package/dist/scripts/module-create.js +25 -15
  21. package/dist/scripts/module-remove.d.ts.map +1 -1
  22. package/dist/scripts/module-remove.js +28 -15
  23. package/dist/scripts/script-runner.js +1 -1
  24. package/dist/styles.css +1 -1
  25. package/dist/templates/DefaultDoc.js +1 -7
  26. package/dist/templates/components/AppAside.d.ts +6 -0
  27. package/dist/templates/components/AppAside.d.ts.map +1 -0
  28. package/dist/templates/components/AppAside.js +9 -0
  29. package/dist/templates/layouts/admin-layout.d.ts +4 -0
  30. package/dist/templates/layouts/admin-layout.d.ts.map +1 -0
  31. package/dist/templates/layouts/admin-layout.js +6 -0
  32. package/dist/templates/layouts/auth-layout.d.ts +4 -0
  33. package/dist/templates/layouts/auth-layout.d.ts.map +1 -0
  34. package/dist/templates/layouts/auth-layout.js +6 -0
  35. package/package.json +2 -1
  36. package/src/app-shell/(public)/page.tsx +6 -2
  37. package/src/auth/useAuthSession.ts +1 -1
  38. package/src/cli.ts +1 -1
  39. package/src/index.ts +6 -0
  40. package/src/layouts/AdminLayoutWithSidebar.tsx +35 -0
  41. package/src/layouts/AppProviders.tsx +1 -1
  42. package/src/layouts/AuthLayoutWithSidebar.tsx +35 -0
  43. package/src/scripts/db-init.ts +13 -8
  44. package/src/scripts/db-migrations-sync.ts +4 -4
  45. package/src/scripts/dev-sync.ts +50 -19
  46. package/src/scripts/init-app.ts +243 -65
  47. package/src/scripts/module-add.ts +54 -22
  48. package/src/scripts/module-build.ts +412 -88
  49. package/src/scripts/module-create.ts +85 -49
  50. package/src/scripts/module-remove.ts +120 -61
  51. package/src/scripts/readme-build.ts +2 -2
  52. package/src/scripts/script-runner.ts +3 -3
  53. package/src/templates/AuthGuidePage.tsx +1 -1
  54. package/src/templates/DefaultDoc.tsx +7 -7
@@ -42,7 +42,7 @@ async function loadModuleConfigs() {
42
42
  break;
43
43
  }
44
44
  }
45
- catch (err) {
45
+ catch {
46
46
  // Essayer le nom suivant
47
47
  }
48
48
  }
@@ -113,7 +113,29 @@ function toPascalCase(value) {
113
113
  .join("");
114
114
  }
115
115
  function buildPage(moduleConfig, page) {
116
- const segments = page.path.replace(/^\/+/, "").split("/").filter(Boolean);
116
+ // Extraire le préfixe du module (ex: @lastbrain/module-auth -> auth)
117
+ const modulePrefix = moduleConfig.moduleName
118
+ .replace(/^@lastbrain\/module-/, "")
119
+ .toLowerCase();
120
+ console.log(`🔄 Building page for module ${modulePrefix}: ${page.path}`);
121
+ // Ajouter le préfixe du module au path pour les sections admin et auth,
122
+ // MAIS seulement quand la section ne correspond PAS au module lui-même
123
+ let effectivePath = page.path;
124
+ if (page.section === "admin" ||
125
+ (page.section === "auth" && modulePrefix !== "auth")) {
126
+ // Éviter les doublons si le préfixe est déjà présent
127
+ if (!page.path.startsWith(`/${modulePrefix}/`)) {
128
+ effectivePath = `/${modulePrefix}${page.path}`;
129
+ console.log(`📂 Added module prefix: ${page.path} -> ${effectivePath}`);
130
+ }
131
+ else {
132
+ console.log(`✅ Module prefix already present: ${page.path}`);
133
+ }
134
+ }
135
+ else if (page.section === "auth" && modulePrefix === "auth") {
136
+ console.log(`🏠 Auth module in auth section, no prefix needed: ${page.path}`);
137
+ }
138
+ const segments = effectivePath.replace(/^\/+/, "").split("/").filter(Boolean);
117
139
  const sectionPath = sectionDirectoryMap[page.section] ?? ["(public)"];
118
140
  // Générer le layout de section si nécessaire
119
141
  ensureSectionLayout(sectionPath);
@@ -125,14 +147,17 @@ function buildPage(moduleConfig, page) {
125
147
  : "Root";
126
148
  const wrapperName = `${page.componentExport}${wrapperSuffix}Route`;
127
149
  // Vérifier si la route a des paramètres dynamiques (segments avec [])
128
- const hasDynamicParams = segments.some(seg => seg.startsWith('[') && seg.endsWith(']'));
150
+ const hasDynamicParams = segments.some((seg) => seg.startsWith("[") && seg.endsWith("]"));
129
151
  // Pour les pages publiques (signin, signup, etc.), utiliser dynamic import sans SSR
130
152
  // pour éviter les erreurs d'hydratation avec les IDs HeroUI/React Aria
131
153
  const isPublicAuthPage = page.section === "public" &&
132
- (page.path.includes("signin") || page.path.includes("signup") || page.path.includes("reset-password"));
154
+ (page.path.includes("signin") ||
155
+ page.path.includes("signup") ||
156
+ page.path.includes("reset-password"));
133
157
  let content;
134
158
  if (isPublicAuthPage) {
135
- content = `"use client";
159
+ content = `// GENERATED BY LASTBRAIN MODULE BUILD
160
+ "use client";
136
161
 
137
162
  import dynamic from "next/dynamic";
138
163
 
@@ -141,16 +166,17 @@ const ${page.componentExport} = dynamic(
141
166
  { ssr: false }
142
167
  );
143
168
 
144
- export default function ${wrapperName}${hasDynamicParams ? '(props: any)' : '()'} {
145
- return <${page.componentExport} ${hasDynamicParams ? '{...props}' : ''} />;
169
+ export default function ${wrapperName}${hasDynamicParams ? "(props: any)" : "()"} {
170
+ return <${page.componentExport} ${hasDynamicParams ? "{...props}" : ""} />;
146
171
  }
147
172
  `;
148
173
  }
149
174
  else {
150
- content = `import { ${page.componentExport} } from "${moduleConfig.moduleName}";
175
+ content = `// GENERATED BY LASTBRAIN MODULE BUILD
176
+ import { ${page.componentExport} } from "${moduleConfig.moduleName}";
151
177
 
152
- export default function ${wrapperName}${hasDynamicParams ? '(props: any)' : '()'} {
153
- return <${page.componentExport} ${hasDynamicParams ? '{...props}' : ''} />;
178
+ export default function ${wrapperName}${hasDynamicParams ? "(props: any)" : "()"} {
179
+ return <${page.componentExport} ${hasDynamicParams ? "{...props}" : ""} />;
154
180
  }
155
181
  `;
156
182
  }
@@ -158,7 +184,7 @@ export default function ${wrapperName}${hasDynamicParams ? '(props: any)' : '()'
158
184
  console.log(`⭐ Generated page: ${filePath}`);
159
185
  const entry = {
160
186
  label: `Module:${moduleConfig.moduleName} ${page.componentExport}`,
161
- path: page.path,
187
+ path: effectivePath,
162
188
  module: moduleConfig.moduleName,
163
189
  section: page.section,
164
190
  };
@@ -169,18 +195,6 @@ export default function ${wrapperName}${hasDynamicParams ? '(props: any)' : '()'
169
195
  navigation[page.section]?.push(entry);
170
196
  }
171
197
  }
172
- function buildApi(moduleConfig, api) {
173
- const segments = api.path.replace(/^\/+/, "").split("/").filter(Boolean);
174
- const sanitizedSegments = segments[0] === "api" ? segments.slice(1) : segments;
175
- const routeDir = path.join(appDirectory, "api", ...sanitizedSegments);
176
- const filePath = path.join(routeDir, "route.ts");
177
- ensureDirectory(routeDir);
178
- const handler = `${moduleConfig.moduleName}/${api.entryPoint}`;
179
- const content = `export { ${api.handlerExport} } from "${handler}";
180
- `;
181
- fs.writeFileSync(filePath, content);
182
- console.log(`🔌 Generated API route: ${filePath}`);
183
- }
184
198
  function dumpNavigation() {
185
199
  const navPath = path.join(appDirectory, "navigation.generated.ts");
186
200
  const content = `export type MenuEntry = { label: string; path: string; module: string; section: string };
@@ -354,8 +368,10 @@ function generateDocsPage(moduleConfigs) {
354
368
  const moduleId = moduleName.replace("@lastbrain/module-", "");
355
369
  const docComponentName = `${toPascalCase(moduleId)}ModuleDoc`;
356
370
  // Trouver la config du module pour obtenir la description
357
- const moduleConfig = moduleConfigs.find(mc => mc.moduleName === moduleName);
358
- const description = moduleConfig ? getModuleDescription(moduleConfig) : "Module non configuré";
371
+ const moduleConfig = moduleConfigs.find((mc) => mc.moduleName === moduleName);
372
+ const description = moduleConfig
373
+ ? getModuleDescription(moduleConfig)
374
+ : "Module non configuré";
359
375
  // Importer le composant Doc seulement pour les modules actifs
360
376
  if (moduleEntry.active) {
361
377
  docImports.push(`import { ${docComponentName} } from "${moduleName}";`);
@@ -371,18 +387,18 @@ function generateDocsPage(moduleConfigs) {
371
387
  id: "${config.id}",
372
388
  name: "${config.name}",
373
389
  description: "${config.description}",
374
- ${config.active ? `content: <${config.component} />,` : 'content: null,'}
390
+ ${config.active ? `content: <${config.component} />,` : "content: null,"}
375
391
  available: ${config.active},
376
392
  }`);
377
393
  });
378
394
  const docsContent = `// Auto-generated docs page with module documentation
379
395
  import React from "react";
380
396
  import { DocPage } from "@lastbrain/app";
381
- ${docImports.join('\n')}
397
+ ${docImports.join("\n")}
382
398
 
383
399
  export default function DocsPage() {
384
400
  const modules = [
385
- ${moduleConfigurations.join(',\n')}
401
+ ${moduleConfigurations.join(",\n")}
386
402
  ];
387
403
 
388
404
  return <DocPage modules={modules} />;
@@ -391,6 +407,33 @@ ${moduleConfigurations.join(',\n')}
391
407
  fs.writeFileSync(docsPagePath, docsContent);
392
408
  console.log(`📚 Generated docs page: ${docsPagePath}`);
393
409
  }
410
+ function buildGroupedApi(apis, routePath) {
411
+ const segments = routePath.replace(/^\/+/, "").split("/").filter(Boolean);
412
+ const sanitizedSegments = segments[0] === "api" ? segments.slice(1) : segments;
413
+ const routeDir = path.join(appDirectory, "api", ...sanitizedSegments);
414
+ const filePath = path.join(routeDir, "route.ts");
415
+ ensureDirectory(routeDir);
416
+ // Grouper par module/entryPoint pour créer les exports
417
+ const exportsBySource = new Map();
418
+ apis.forEach(({ moduleConfig, api }) => {
419
+ const handler = `${moduleConfig.moduleName}/${api.entryPoint}`;
420
+ if (!exportsBySource.has(handler)) {
421
+ exportsBySource.set(handler, []);
422
+ }
423
+ exportsBySource.get(handler).push(api.handlerExport);
424
+ });
425
+ // Générer les exports - un export statement par source
426
+ const exportStatements = [];
427
+ exportsBySource.forEach((exports, source) => {
428
+ exportStatements.push(`export { ${exports.join(", ")} } from "${source}";`);
429
+ });
430
+ const content = exportStatements.join("\n") + "\n";
431
+ // Ajouter le marqueur de génération au début
432
+ const contentWithMarker = `// GENERATED BY LASTBRAIN MODULE BUILD
433
+ ${content}`;
434
+ fs.writeFileSync(filePath, contentWithMarker);
435
+ console.log(`🔌 Generated API route: ${filePath}`);
436
+ }
394
437
  function getModuleDescription(moduleConfig) {
395
438
  // Essayer de déduire la description depuis les pages ou retourner une description par défaut
396
439
  if (moduleConfig.pages.length > 0) {
@@ -398,16 +441,231 @@ function getModuleDescription(moduleConfig) {
398
441
  }
399
442
  return "Module documentation";
400
443
  }
444
+ function cleanGeneratedFiles() {
445
+ const generatedComment = "// GENERATED BY LASTBRAIN MODULE BUILD";
446
+ // Fichiers de base à préserver (paths relatifs exacts)
447
+ const protectedFiles = new Set([
448
+ // API de base
449
+ "api/storage",
450
+ // Layouts de base
451
+ "layout.tsx",
452
+ "not-found.tsx",
453
+ "page.tsx", // Page racine seulement
454
+ "admin/page.tsx", // Page admin racine
455
+ "admin/layout.tsx", // Layout admin racine
456
+ "docs/page.tsx", // Page docs générée
457
+ // Middleware et autres fichiers core
458
+ "middleware.ts",
459
+ // Dossiers de lib et config
460
+ "lib",
461
+ "config",
462
+ ]);
463
+ // Fonction pour vérifier si un chemin est protégé
464
+ const isProtected = (filePath) => {
465
+ const relativePath = path.relative(appDirectory, filePath);
466
+ // Protection exacte pour certains fichiers
467
+ if (protectedFiles.has(relativePath)) {
468
+ return true;
469
+ }
470
+ // Protection par préfixe pour les dossiers
471
+ return Array.from(protectedFiles).some((protectedPath) => (protectedPath.endsWith("/") ||
472
+ ["lib", "config", "api/storage"].includes(protectedPath)) &&
473
+ relativePath.startsWith(protectedPath));
474
+ };
475
+ // Fonction pour nettoyer récursivement un dossier
476
+ const cleanDirectory = (dirPath) => {
477
+ if (!fs.existsSync(dirPath))
478
+ return;
479
+ const items = fs.readdirSync(dirPath);
480
+ for (const item of items) {
481
+ const itemPath = path.join(dirPath, item);
482
+ const stat = fs.statSync(itemPath);
483
+ if (stat.isDirectory()) {
484
+ // Nettoyer récursivement le sous-dossier
485
+ cleanDirectory(itemPath);
486
+ // Supprimer le dossier s'il est vide et non protégé
487
+ try {
488
+ if (!isProtected(itemPath) && fs.readdirSync(itemPath).length === 0) {
489
+ fs.rmdirSync(itemPath);
490
+ console.log(`🗑️ Removed empty directory: ${itemPath}`);
491
+ }
492
+ }
493
+ catch {
494
+ // Ignorer les erreurs de suppression de fichiers
495
+ }
496
+ }
497
+ else if (item.endsWith(".tsx") || item.endsWith(".ts")) {
498
+ // Vérifier si c'est un fichier généré
499
+ if (!isProtected(itemPath)) {
500
+ try {
501
+ const content = fs.readFileSync(itemPath, "utf-8");
502
+ // Supprimer les fichiers générés ou les wrapper simples de modules
503
+ if (content.includes(generatedComment) ||
504
+ content.includes('from "@lastbrain/module-') ||
505
+ (content.includes("export {") &&
506
+ content.includes('} from "@lastbrain/module-'))) {
507
+ fs.unlinkSync(itemPath);
508
+ console.log(`🗑️ Cleaned generated file: ${itemPath}`);
509
+ }
510
+ }
511
+ catch {
512
+ // Ignorer les erreurs de lecture/suppression
513
+ }
514
+ }
515
+ else {
516
+ console.log(`🔒 Protected file skipped: ${itemPath}`);
517
+ }
518
+ }
519
+ }
520
+ };
521
+ // Nettoyer les dossiers de sections
522
+ const sectionsToClean = ["(public)", "auth", "admin", "api"];
523
+ sectionsToClean.forEach((section) => {
524
+ const sectionPath = path.join(appDirectory, section);
525
+ if (fs.existsSync(sectionPath)) {
526
+ cleanDirectory(sectionPath);
527
+ }
528
+ });
529
+ // Nettoyer les fichiers générés à la racine
530
+ const rootFiles = ["navigation.generated.ts"];
531
+ rootFiles.forEach((file) => {
532
+ const filePath = path.join(appDirectory, file);
533
+ if (fs.existsSync(filePath)) {
534
+ fs.unlinkSync(filePath);
535
+ console.log(`🗑️ Cleaned root file: ${filePath}`);
536
+ }
537
+ });
538
+ console.log("🧹 Cleanup completed");
539
+ }
540
+ function generateAppAside() {
541
+ const targetPath = path.join(appDirectory, "components", "AppAside.tsx");
542
+ // Ne pas écraser si le fichier existe déjà
543
+ if (fs.existsSync(targetPath)) {
544
+ console.log(`⏭️ AppAside already exists, skipping: ${targetPath}`);
545
+ return;
546
+ }
547
+ const templateContent = `"use client";
548
+
549
+ import { AppAside as UIAppAside } from "@lastbrain/app";
550
+ import { useAuthSession } from "@lastbrain/app";
551
+ import { menuConfig } from "../../config/menu";
552
+
553
+ interface AppAsideProps {
554
+ className?: string;
555
+ isVisible?: boolean;
556
+ }
557
+
558
+ export function AppAside({ className = "", isVisible = true }: AppAsideProps) {
559
+ const { isSuperAdmin } = useAuthSession();
560
+
561
+ return (
562
+ <UIAppAside
563
+ className={className}
564
+ menuConfig={menuConfig}
565
+ isSuperAdmin={isSuperAdmin}
566
+ isVisible={isVisible}
567
+ />
568
+ );
569
+ }`;
570
+ try {
571
+ ensureDirectory(path.dirname(targetPath));
572
+ fs.writeFileSync(targetPath, templateContent, "utf-8");
573
+ console.log(`✅ Generated AppAside component: ${targetPath}`);
574
+ }
575
+ catch (error) {
576
+ console.error(`❌ Error generating AppAside component: ${error}`);
577
+ }
578
+ }
579
+ function generateLayouts() {
580
+ // Générer layout auth avec sidebar
581
+ const authLayoutPath = path.join(appDirectory, "auth", "layout.tsx");
582
+ if (!fs.existsSync(authLayoutPath)) {
583
+ const authLayoutContent = `import { AuthLayoutWithSidebar } from "@lastbrain/app";
584
+ import { menuConfig } from "../../config/menu";
585
+
586
+ export default function SectionLayout({
587
+ children,
588
+ }: {
589
+ children: React.ReactNode;
590
+ }) {
591
+ return (
592
+ <AuthLayoutWithSidebar menuConfig={menuConfig}>
593
+ {children}
594
+ </AuthLayoutWithSidebar>
595
+ );
596
+ }`;
597
+ try {
598
+ ensureDirectory(path.dirname(authLayoutPath));
599
+ fs.writeFileSync(authLayoutPath, authLayoutContent, "utf-8");
600
+ console.log(`✅ Generated auth layout with sidebar: ${authLayoutPath}`);
601
+ }
602
+ catch (error) {
603
+ console.error(`❌ Error generating auth layout: ${error}`);
604
+ }
605
+ }
606
+ else {
607
+ console.log(`⏭️ Auth layout already exists, skipping: ${authLayoutPath}`);
608
+ }
609
+ // Générer layout admin avec sidebar
610
+ const adminLayoutPath = path.join(appDirectory, "admin", "layout.tsx");
611
+ if (!fs.existsSync(adminLayoutPath)) {
612
+ const adminLayoutContent = `import { AdminLayoutWithSidebar } from "@lastbrain/app";
613
+ import { menuConfig } from "../../config/menu";
614
+
615
+ export default function AdminLayout({
616
+ children,
617
+ }: {
618
+ children: React.ReactNode;
619
+ }) {
620
+ return (
621
+ <AdminLayoutWithSidebar menuConfig={menuConfig}>
622
+ {children}
623
+ </AdminLayoutWithSidebar>
624
+ );
625
+ }`;
626
+ try {
627
+ ensureDirectory(path.dirname(adminLayoutPath));
628
+ fs.writeFileSync(adminLayoutPath, adminLayoutContent, "utf-8");
629
+ console.log(`✅ Generated admin layout with sidebar: ${adminLayoutPath}`);
630
+ }
631
+ catch (error) {
632
+ console.error(`❌ Error generating admin layout: ${error}`);
633
+ }
634
+ }
635
+ else {
636
+ console.log(`⏭️ Admin layout already exists, skipping: ${adminLayoutPath}`);
637
+ }
638
+ }
401
639
  export async function runModuleBuild() {
402
640
  ensureDirectory(appDirectory);
641
+ // Nettoyer les fichiers générés précédemment
642
+ console.log("🧹 Cleaning previously generated files...");
643
+ cleanGeneratedFiles();
403
644
  const moduleConfigs = await loadModuleConfigs();
645
+ console.log(`🔍 Loaded ${moduleConfigs.length} module configurations`);
646
+ // Générer les pages
404
647
  moduleConfigs.forEach((moduleConfig) => {
648
+ console.log(`📦 Processing module: ${moduleConfig.moduleName} with ${moduleConfig.pages.length} pages`);
405
649
  moduleConfig.pages.forEach((page) => buildPage(moduleConfig, page));
406
- moduleConfig.apis.forEach((api) => buildApi(moduleConfig, api));
650
+ });
651
+ // Grouper les APIs par chemin pour éviter les écrasements de fichier
652
+ const apisByPath = new Map();
653
+ moduleConfigs.forEach((moduleConfig) => {
654
+ moduleConfig.apis.forEach((api) => {
655
+ if (!apisByPath.has(api.path)) {
656
+ apisByPath.set(api.path, []);
657
+ }
658
+ apisByPath.get(api.path).push({ moduleConfig, api });
659
+ });
660
+ });
661
+ // Générer les fichiers de route groupés
662
+ apisByPath.forEach((apis, routePath) => {
663
+ buildGroupedApi(apis, routePath);
407
664
  });
408
665
  dumpNavigation();
409
666
  generateMenuConfig(moduleConfigs);
410
667
  generateDocsPage(moduleConfigs);
668
+ generateAppAside();
669
+ generateLayouts();
411
670
  copyModuleMigrations(moduleConfigs);
412
671
  }
413
- runModuleBuild();
@@ -1 +1 @@
1
- {"version":3,"file":"module-create.d.ts","sourceRoot":"","sources":["../../src/scripts/module-create.ts"],"names":[],"mappings":"AAirBA;;GAEG;AACH,wBAAsB,YAAY,kBAsIjC"}
1
+ {"version":3,"file":"module-create.d.ts","sourceRoot":"","sources":["../../src/scripts/module-create.ts"],"names":[],"mappings":"AAktBA;;GAEG;AACH,wBAAsB,YAAY,kBAyIjC"}
@@ -38,7 +38,7 @@ function getLastBrainPackageVersions(rootDir) {
38
38
  ui: `^${uiPackageJson.version}`,
39
39
  };
40
40
  }
41
- catch (error) {
41
+ catch {
42
42
  console.warn(chalk.yellow("⚠️ Impossible de lire les versions des packages, utilisation des versions par défaut"));
43
43
  return {
44
44
  core: "^0.1.0",
@@ -145,7 +145,7 @@ function generateBuildConfig(config) {
145
145
  // Générer les menus par section
146
146
  const menuSections = [];
147
147
  // Menu public
148
- const publicPages = pages.filter(p => p.section === "public");
148
+ const publicPages = pages.filter((p) => p.section === "public");
149
149
  if (publicPages.length > 0) {
150
150
  const publicMenuItems = publicPages.map((page, index) => {
151
151
  const title = page.name
@@ -163,7 +163,7 @@ function generateBuildConfig(config) {
163
163
  menuSections.push({ section: "public", items: publicMenuItems });
164
164
  }
165
165
  // Menu auth
166
- const authPages = pages.filter(p => p.section === "auth");
166
+ const authPages = pages.filter((p) => p.section === "auth");
167
167
  if (authPages.length > 0) {
168
168
  const authMenuItems = authPages.map((page, index) => {
169
169
  const title = page.name
@@ -181,7 +181,7 @@ function generateBuildConfig(config) {
181
181
  menuSections.push({ section: "auth", items: authMenuItems });
182
182
  }
183
183
  // Menu admin
184
- const adminPages = pages.filter(p => p.section === "admin");
184
+ const adminPages = pages.filter((p) => p.section === "admin");
185
185
  if (adminPages.length > 0) {
186
186
  const adminMenuItems = adminPages.map((page, index) => {
187
187
  const title = page.name
@@ -202,9 +202,11 @@ function generateBuildConfig(config) {
202
202
  const menuConfig = menuSections.length > 0
203
203
  ? `,
204
204
  menu: {
205
- ${menuSections.map(({ section, items }) => ` ${section}: [
205
+ ${menuSections
206
+ .map(({ section, items }) => ` ${section}: [
206
207
  ${items.join(",\n")}
207
- ]`).join(",\n")}
208
+ ]`)
209
+ .join(",\n")}
208
210
  }`
209
211
  : "";
210
212
  return `import type { ModuleBuildConfig } from "@lastbrain/core";
@@ -315,12 +317,14 @@ const jsonResponse = (payload: unknown, status = 200) => {
315
317
  */
316
318
  export async function GET(request: Request) {
317
319
  const supabase = await getSupabaseServerClient();
318
- ${authRequired ? `
320
+ ${authRequired
321
+ ? `
319
322
  const { data: { user }, error: authError } = await supabase.auth.getUser();
320
323
  if (authError || !user) {
321
324
  return jsonResponse({ error: "Non authentifié" }, 401);
322
325
  }
323
- ` : ""}
326
+ `
327
+ : ""}
324
328
 
325
329
  const { data, error } = await supabase
326
330
  .from("${tableName}")
@@ -338,12 +342,14 @@ export async function GET(request: Request) {
338
342
  */
339
343
  export async function POST(request: Request) {
340
344
  const supabase = await getSupabaseServerClient();
341
- ${authRequired ? `
345
+ ${authRequired
346
+ ? `
342
347
  const { data: { user }, error: authError } = await supabase.auth.getUser();
343
348
  if (authError || !user) {
344
349
  return jsonResponse({ error: "Non authentifié" }, 401);
345
350
  }
346
- ` : ""}
351
+ `
352
+ : ""}
347
353
 
348
354
  const body = await request.json();
349
355
 
@@ -365,12 +371,14 @@ export async function POST(request: Request) {
365
371
  */
366
372
  export async function PUT(request: Request) {
367
373
  const supabase = await getSupabaseServerClient();
368
- ${authRequired ? `
374
+ ${authRequired
375
+ ? `
369
376
  const { data: { user }, error: authError } = await supabase.auth.getUser();
370
377
  if (authError || !user) {
371
378
  return jsonResponse({ error: "Non authentifié" }, 401);
372
379
  }
373
- ` : ""}
380
+ `
381
+ : ""}
374
382
 
375
383
  const body = await request.json();
376
384
  const { id, ...updateData } = body;
@@ -398,12 +406,14 @@ export async function PUT(request: Request) {
398
406
  */
399
407
  export async function DELETE(request: Request) {
400
408
  const supabase = await getSupabaseServerClient();
401
- ${authRequired ? `
409
+ ${authRequired
410
+ ? `
402
411
  const { data: { user }, error: authError } = await supabase.auth.getUser();
403
412
  if (authError || !user) {
404
413
  return jsonResponse({ error: "Non authentifié" }, 401);
405
414
  }
406
- ` : ""}
415
+ `
416
+ : ""}
407
417
 
408
418
  const { searchParams } = new URL(request.url);
409
419
  const id = searchParams.get("id");
@@ -429,7 +439,7 @@ export async function DELETE(request: Request) {
429
439
  * Génère le contenu d'un fichier de migration SQL
430
440
  */
431
441
  function generateMigration(tables, slug) {
432
- const timestamp = new Date()
442
+ const _timestamp = new Date()
433
443
  .toISOString()
434
444
  .replace(/[-:]/g, "")
435
445
  .split(".")[0]
@@ -1 +1 @@
1
- {"version":3,"file":"module-remove.d.ts","sourceRoot":"","sources":["../../src/scripts/module-remove.ts"],"names":[],"mappings":"AAmEA,wBAAsB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,iBA2SvE"}
1
+ {"version":3,"file":"module-remove.d.ts","sourceRoot":"","sources":["../../src/scripts/module-remove.ts"],"names":[],"mappings":"AAmEA,wBAAsB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,iBAsWvE"}
@@ -36,7 +36,7 @@ async function removeGeneratedFiles(moduleName, modulePackage, targetDir) {
36
36
  }
37
37
  }
38
38
  }
39
- catch (error) {
39
+ catch {
40
40
  // Ignorer les erreurs de lecture
41
41
  }
42
42
  }
@@ -105,10 +105,12 @@ export async function removeModule(moduleName, targetDir) {
105
105
  let migrationFiles = [];
106
106
  if (fs.existsSync(modulesConfigPath)) {
107
107
  const modulesConfig = await fs.readJson(modulesConfigPath);
108
- const moduleEntry = modulesConfig.modules.find((m) => (typeof m === 'string' ? m : m.package) === module.package);
109
- if (moduleEntry && typeof moduleEntry === 'object' && moduleEntry.migrations) {
108
+ const moduleEntry = modulesConfig.modules.find((m) => (typeof m === "string" ? m : m.package) === module.package);
109
+ if (moduleEntry &&
110
+ typeof moduleEntry === "object" &&
111
+ moduleEntry.migrations) {
110
112
  migrationFiles = moduleEntry.migrations;
111
- console.log(chalk.gray(`DEBUG: Migrations from config: ${migrationFiles.join(', ')}`));
113
+ console.log(chalk.gray(`DEBUG: Migrations from config: ${migrationFiles.join(", ")}`));
112
114
  }
113
115
  }
114
116
  // Fallback: essayer de lire depuis node_modules si disponible
@@ -119,7 +121,7 @@ export async function removeModule(moduleName, targetDir) {
119
121
  migrationFiles = fs
120
122
  .readdirSync(moduleMigrationsDir)
121
123
  .filter((f) => f.endsWith(".sql"));
122
- console.log(chalk.gray(`DEBUG: Found ${migrationFiles.length} migration files from node_modules: ${migrationFiles.join(', ')}`));
124
+ console.log(chalk.gray(`DEBUG: Found ${migrationFiles.length} migration files from node_modules: ${migrationFiles.join(", ")}`));
123
125
  }
124
126
  else {
125
127
  console.log(chalk.gray(`DEBUG: No migrations found in config or node_modules`));
@@ -170,7 +172,7 @@ export async function removeModule(moduleName, targetDir) {
170
172
  for (const downFile of downFiles) {
171
173
  const downFilePath = path.join(moduleMigrationsDownDir, downFile);
172
174
  // Extraire le timestamp de la migration (14 premiers caractères)
173
- const migrationVersion = downFile.split('_')[0]; // Prend la partie avant le premier underscore
175
+ const migrationVersion = downFile.split("_")[0]; // Prend la partie avant le premier underscore
174
176
  try {
175
177
  console.log(chalk.gray(` Exécution de ${downFile}...`));
176
178
  execSync(`psql "postgresql://postgres:postgres@127.0.0.1:54322/postgres" -f "${downFilePath}"`, {
@@ -182,7 +184,7 @@ export async function removeModule(moduleName, targetDir) {
182
184
  const deleteQuery = `DELETE FROM supabase_migrations.schema_migrations WHERE version = '${migrationVersion}';`;
183
185
  const result = execSync(`psql "postgresql://postgres:postgres@127.0.0.1:54322/postgres" -c "${deleteQuery}"`, {
184
186
  cwd: targetDir,
185
- encoding: 'utf-8',
187
+ encoding: "utf-8",
186
188
  });
187
189
  console.log(chalk.gray(` ✓ Supprimé ${migrationVersion} de schema_migrations (${result.trim()})`));
188
190
  }
@@ -191,7 +193,7 @@ export async function removeModule(moduleName, targetDir) {
191
193
  }
192
194
  console.log(chalk.green(` ✓ ${downFile}`));
193
195
  }
194
- catch (error) {
196
+ catch {
195
197
  console.error(chalk.red(` ❌ Erreur avec ${downFile}`));
196
198
  }
197
199
  }
@@ -211,7 +213,7 @@ export async function removeModule(moduleName, targetDir) {
211
213
  execSync("supabase db reset", { cwd: targetDir, stdio: "inherit" });
212
214
  console.log(chalk.green("✓ Base de données réinitialisée"));
213
215
  }
214
- catch (error) {
216
+ catch {
215
217
  console.error(chalk.red("❌ Erreur lors du reset"));
216
218
  }
217
219
  }
@@ -232,15 +234,17 @@ export async function removeModule(moduleName, targetDir) {
232
234
  if (migrationFiles.length > 0) {
233
235
  console.log(chalk.yellow("\n🗄️ Nettoyage de schema_migrations..."));
234
236
  // Extraire uniquement les timestamps (14 premiers caractères) de chaque fichier de migration
235
- const versions = migrationFiles.map(f => {
236
- const timestamp = f.split('_')[0]; // Prend la partie avant le premier underscore
237
+ const versions = migrationFiles
238
+ .map((f) => {
239
+ const timestamp = f.split("_")[0]; // Prend la partie avant le premier underscore
237
240
  return `'${timestamp}'`;
238
- }).join(', ');
241
+ })
242
+ .join(", ");
239
243
  const deleteQuery = `DELETE FROM supabase_migrations.schema_migrations WHERE version IN (${versions});`;
240
244
  try {
241
245
  const result = execSync(`psql "postgresql://postgres:postgres@127.0.0.1:54322/postgres" -c "${deleteQuery}"`, {
242
246
  cwd: targetDir,
243
- encoding: 'utf-8',
247
+ encoding: "utf-8",
244
248
  });
245
249
  console.log(chalk.gray(` ✓ Supprimé ${migrationFiles.length} entrées de schema_migrations`));
246
250
  console.log(chalk.gray(` ${result.trim()}`));
@@ -264,7 +268,7 @@ export async function removeModule(moduleName, targetDir) {
264
268
  fs.writeFileSync(modulesJsonPath, JSON.stringify(modulesConfig, null, 2));
265
269
  console.log(chalk.gray("\n✓ Module marqué comme inactif dans modules.json"));
266
270
  }
267
- catch (error) {
271
+ catch {
268
272
  console.error(chalk.red("❌ Erreur lors de la mise à jour de modules.json"));
269
273
  }
270
274
  }
@@ -273,10 +277,19 @@ export async function removeModule(moduleName, targetDir) {
273
277
  try {
274
278
  execSync("pnpm install", { cwd: targetDir, stdio: "inherit" });
275
279
  }
276
- catch (error) {
280
+ catch {
277
281
  console.error(chalk.red("❌ Erreur lors du nettoyage"));
278
282
  process.exit(1);
279
283
  }
280
284
  console.log(chalk.green(`\n✅ Module ${module.displayName} supprimé avec succès!\n`));
285
+ // 7. Rebuilder les modules pour mettre à jour les fichiers générés
286
+ console.log(chalk.yellow("🔧 Mise à jour des modules..."));
287
+ try {
288
+ execSync("pnpm run build:modules", { cwd: targetDir, stdio: "inherit" });
289
+ console.log(chalk.green("✓ Modules mis à jour"));
290
+ }
291
+ catch {
292
+ console.warn(chalk.yellow("⚠️ Erreur lors de la mise à jour des modules"));
293
+ }
281
294
  console.log(chalk.gray("Le serveur de développement redémarrera automatiquement.\n"));
282
295
  }
@@ -8,7 +8,7 @@ export async function runScript(scriptName, args = []) {
8
8
  const scriptPath = path.join(__dirname, `${scriptName}.js`);
9
9
  const child = spawn("node", [scriptPath, ...args], {
10
10
  stdio: "inherit",
11
- env: { ...process.env, PROJECT_ROOT: process.cwd() }
11
+ env: { ...process.env, PROJECT_ROOT: process.cwd() },
12
12
  });
13
13
  child.on("close", (code) => {
14
14
  if (code === 0) {