@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.
- package/dist/app-shell/(public)/page.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/layouts/AdminLayoutWithSidebar.d.ts +8 -0
- package/dist/layouts/AdminLayoutWithSidebar.d.ts.map +1 -0
- package/dist/layouts/AdminLayoutWithSidebar.js +9 -0
- package/dist/layouts/AuthLayoutWithSidebar.d.ts +8 -0
- package/dist/layouts/AuthLayoutWithSidebar.d.ts.map +1 -0
- package/dist/layouts/AuthLayoutWithSidebar.js +9 -0
- package/dist/scripts/db-init.js +2 -2
- package/dist/scripts/dev-sync.js +23 -12
- package/dist/scripts/init-app.d.ts.map +1 -1
- package/dist/scripts/init-app.js +114 -12
- package/dist/scripts/module-add.d.ts.map +1 -1
- package/dist/scripts/module-add.js +19 -6
- package/dist/scripts/module-build.d.ts.map +1 -1
- package/dist/scripts/module-build.js +288 -30
- package/dist/scripts/module-create.d.ts.map +1 -1
- package/dist/scripts/module-create.js +25 -15
- package/dist/scripts/module-remove.d.ts.map +1 -1
- package/dist/scripts/module-remove.js +28 -15
- package/dist/scripts/script-runner.js +1 -1
- package/dist/styles.css +1 -1
- package/dist/templates/DefaultDoc.js +1 -7
- package/dist/templates/components/AppAside.d.ts +6 -0
- package/dist/templates/components/AppAside.d.ts.map +1 -0
- package/dist/templates/components/AppAside.js +9 -0
- package/dist/templates/layouts/admin-layout.d.ts +4 -0
- package/dist/templates/layouts/admin-layout.d.ts.map +1 -0
- package/dist/templates/layouts/admin-layout.js +6 -0
- package/dist/templates/layouts/auth-layout.d.ts +4 -0
- package/dist/templates/layouts/auth-layout.d.ts.map +1 -0
- package/dist/templates/layouts/auth-layout.js +6 -0
- package/package.json +2 -1
- package/src/app-shell/(public)/page.tsx +6 -2
- package/src/auth/useAuthSession.ts +1 -1
- package/src/cli.ts +1 -1
- package/src/index.ts +6 -0
- package/src/layouts/AdminLayoutWithSidebar.tsx +35 -0
- package/src/layouts/AppProviders.tsx +1 -1
- package/src/layouts/AuthLayoutWithSidebar.tsx +35 -0
- package/src/scripts/db-init.ts +13 -8
- package/src/scripts/db-migrations-sync.ts +4 -4
- package/src/scripts/dev-sync.ts +50 -19
- package/src/scripts/init-app.ts +243 -65
- package/src/scripts/module-add.ts +54 -22
- package/src/scripts/module-build.ts +412 -88
- package/src/scripts/module-create.ts +85 -49
- package/src/scripts/module-remove.ts +120 -61
- package/src/scripts/readme-build.ts +2 -2
- package/src/scripts/script-runner.ts +3 -3
- package/src/templates/AuthGuidePage.tsx +1 -1
- package/src/templates/DefaultDoc.tsx +7 -7
|
@@ -42,7 +42,7 @@ async function loadModuleConfigs() {
|
|
|
42
42
|
break;
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
-
catch
|
|
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
|
-
|
|
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(
|
|
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") ||
|
|
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 =
|
|
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 ?
|
|
145
|
-
return <${page.componentExport} ${hasDynamicParams ?
|
|
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 =
|
|
175
|
+
content = `// GENERATED BY LASTBRAIN MODULE BUILD
|
|
176
|
+
import { ${page.componentExport} } from "${moduleConfig.moduleName}";
|
|
151
177
|
|
|
152
|
-
export default function ${wrapperName}${hasDynamicParams ?
|
|
153
|
-
return <${page.componentExport} ${hasDynamicParams ?
|
|
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:
|
|
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
|
|
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} />,` :
|
|
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(
|
|
397
|
+
${docImports.join("\n")}
|
|
382
398
|
|
|
383
399
|
export default function DocsPage() {
|
|
384
400
|
const modules = [
|
|
385
|
-
${moduleConfigurations.join(
|
|
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
|
-
|
|
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":"
|
|
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
|
|
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
|
|
205
|
+
${menuSections
|
|
206
|
+
.map(({ section, items }) => ` ${section}: [
|
|
206
207
|
${items.join(",\n")}
|
|
207
|
-
]`)
|
|
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
|
|
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,
|
|
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
|
|
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 ===
|
|
109
|
-
if (moduleEntry &&
|
|
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(
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
236
|
-
|
|
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
|
-
})
|
|
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:
|
|
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
|
|
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
|
|
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) {
|