@lastbrain/app 0.1.24 → 0.1.26
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/__tests__/module-registry.test.d.ts +2 -0
- package/dist/__tests__/module-registry.test.d.ts.map +1 -0
- package/dist/__tests__/module-registry.test.js +64 -0
- package/dist/app-shell/(admin)/layout.d.ts +3 -2
- package/dist/app-shell/(admin)/layout.d.ts.map +1 -1
- package/dist/app-shell/(admin)/layout.js +1 -1
- package/dist/app-shell/(auth)/layout.d.ts +3 -2
- package/dist/app-shell/(auth)/layout.d.ts.map +1 -1
- package/dist/app-shell/(auth)/layout.js +1 -1
- package/dist/app-shell/(public)/page.d.ts.map +1 -1
- package/dist/cli.js +50 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/layouts/AdminLayout.d.ts +3 -2
- package/dist/layouts/AdminLayout.d.ts.map +1 -1
- 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/AppProviders.d.ts +3 -2
- package/dist/layouts/AppProviders.d.ts.map +1 -1
- package/dist/layouts/AuthLayout.d.ts +3 -2
- package/dist/layouts/AuthLayout.d.ts.map +1 -1
- 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/layouts/PublicLayout.d.ts +3 -2
- package/dist/layouts/PublicLayout.d.ts.map +1 -1
- package/dist/layouts/RootLayout.d.ts +3 -2
- package/dist/layouts/RootLayout.d.ts.map +1 -1
- package/dist/scripts/db-init.js +2 -2
- package/dist/scripts/db-migrations-sync.js +5 -5
- package/dist/scripts/dev-sync.js +21 -10
- package/dist/scripts/init-app.d.ts.map +1 -1
- package/dist/scripts/init-app.js +126 -21
- package/dist/scripts/module-add.d.ts.map +1 -1
- package/dist/scripts/module-add.js +20 -7
- package/dist/scripts/module-build.d.ts.map +1 -1
- package/dist/scripts/module-build.js +285 -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 +24 -11
- package/dist/scripts/script-runner.d.ts +5 -0
- package/dist/scripts/script-runner.d.ts.map +1 -0
- package/dist/scripts/script-runner.js +25 -0
- package/dist/styles.css +1 -1
- package/dist/templates/DefaultDoc.js +1 -7
- package/dist/templates/DocPage.d.ts.map +1 -1
- package/dist/templates/DocPage.js +14 -14
- 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/__tests__/module-registry.test.ts +74 -0
- package/src/app-shell/(admin)/layout.tsx +5 -3
- package/src/app-shell/(auth)/layout.tsx +5 -3
- package/src/app-shell/(public)/page.tsx +6 -2
- package/src/auth/useAuthSession.ts +1 -1
- package/src/cli.ts +51 -1
- package/src/index.ts +6 -0
- package/src/layouts/AdminLayout.tsx +1 -3
- package/src/layouts/AdminLayoutWithSidebar.tsx +35 -0
- package/src/layouts/AppProviders.tsx +3 -5
- package/src/layouts/AuthLayout.tsx +1 -3
- package/src/layouts/AuthLayoutWithSidebar.tsx +35 -0
- package/src/layouts/PublicLayout.tsx +1 -3
- package/src/layouts/RootLayout.tsx +1 -2
- package/src/scripts/db-init.ts +13 -8
- package/src/scripts/db-migrations-sync.ts +4 -4
- package/src/scripts/dev-sync.ts +49 -18
- package/src/scripts/init-app.ts +246 -73
- package/src/scripts/module-add.ts +49 -23
- package/src/scripts/module-build.ts +393 -88
- package/src/scripts/module-create.ts +85 -49
- package/src/scripts/module-remove.ts +116 -57
- package/src/scripts/readme-build.ts +2 -2
- package/src/scripts/script-runner.ts +28 -0
- package/src/templates/AuthGuidePage.tsx +1 -1
- package/src/templates/DefaultDoc.tsx +7 -7
- package/src/templates/DocPage.tsx +74 -46
|
@@ -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,28 @@ 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' || (page.section === 'auth' && modulePrefix !== 'auth')) {
|
|
125
|
+
// Éviter les doublons si le préfixe est déjà présent
|
|
126
|
+
if (!page.path.startsWith(`/${modulePrefix}/`)) {
|
|
127
|
+
effectivePath = `/${modulePrefix}${page.path}`;
|
|
128
|
+
console.log(`📂 Added module prefix: ${page.path} -> ${effectivePath}`);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
console.log(`✅ Module prefix already present: ${page.path}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
else if (page.section === 'auth' && modulePrefix === 'auth') {
|
|
135
|
+
console.log(`🏠 Auth module in auth section, no prefix needed: ${page.path}`);
|
|
136
|
+
}
|
|
137
|
+
const segments = effectivePath.replace(/^\/+/, "").split("/").filter(Boolean);
|
|
117
138
|
const sectionPath = sectionDirectoryMap[page.section] ?? ["(public)"];
|
|
118
139
|
// Générer le layout de section si nécessaire
|
|
119
140
|
ensureSectionLayout(sectionPath);
|
|
@@ -125,14 +146,17 @@ function buildPage(moduleConfig, page) {
|
|
|
125
146
|
: "Root";
|
|
126
147
|
const wrapperName = `${page.componentExport}${wrapperSuffix}Route`;
|
|
127
148
|
// Vérifier si la route a des paramètres dynamiques (segments avec [])
|
|
128
|
-
const hasDynamicParams = segments.some(seg => seg.startsWith(
|
|
149
|
+
const hasDynamicParams = segments.some((seg) => seg.startsWith("[") && seg.endsWith("]"));
|
|
129
150
|
// Pour les pages publiques (signin, signup, etc.), utiliser dynamic import sans SSR
|
|
130
151
|
// pour éviter les erreurs d'hydratation avec les IDs HeroUI/React Aria
|
|
131
152
|
const isPublicAuthPage = page.section === "public" &&
|
|
132
|
-
(page.path.includes("signin") ||
|
|
153
|
+
(page.path.includes("signin") ||
|
|
154
|
+
page.path.includes("signup") ||
|
|
155
|
+
page.path.includes("reset-password"));
|
|
133
156
|
let content;
|
|
134
157
|
if (isPublicAuthPage) {
|
|
135
|
-
content =
|
|
158
|
+
content = `// GENERATED BY LASTBRAIN MODULE BUILD
|
|
159
|
+
"use client";
|
|
136
160
|
|
|
137
161
|
import dynamic from "next/dynamic";
|
|
138
162
|
|
|
@@ -141,16 +165,17 @@ const ${page.componentExport} = dynamic(
|
|
|
141
165
|
{ ssr: false }
|
|
142
166
|
);
|
|
143
167
|
|
|
144
|
-
export default function ${wrapperName}${hasDynamicParams ?
|
|
145
|
-
return <${page.componentExport} ${hasDynamicParams ?
|
|
168
|
+
export default function ${wrapperName}${hasDynamicParams ? "(props: any)" : "()"} {
|
|
169
|
+
return <${page.componentExport} ${hasDynamicParams ? "{...props}" : ""} />;
|
|
146
170
|
}
|
|
147
171
|
`;
|
|
148
172
|
}
|
|
149
173
|
else {
|
|
150
|
-
content =
|
|
174
|
+
content = `// GENERATED BY LASTBRAIN MODULE BUILD
|
|
175
|
+
import { ${page.componentExport} } from "${moduleConfig.moduleName}";
|
|
151
176
|
|
|
152
|
-
export default function ${wrapperName}${hasDynamicParams ?
|
|
153
|
-
return <${page.componentExport} ${hasDynamicParams ?
|
|
177
|
+
export default function ${wrapperName}${hasDynamicParams ? "(props: any)" : "()"} {
|
|
178
|
+
return <${page.componentExport} ${hasDynamicParams ? "{...props}" : ""} />;
|
|
154
179
|
}
|
|
155
180
|
`;
|
|
156
181
|
}
|
|
@@ -158,7 +183,7 @@ export default function ${wrapperName}${hasDynamicParams ? '(props: any)' : '()'
|
|
|
158
183
|
console.log(`⭐ Generated page: ${filePath}`);
|
|
159
184
|
const entry = {
|
|
160
185
|
label: `Module:${moduleConfig.moduleName} ${page.componentExport}`,
|
|
161
|
-
path:
|
|
186
|
+
path: effectivePath,
|
|
162
187
|
module: moduleConfig.moduleName,
|
|
163
188
|
section: page.section,
|
|
164
189
|
};
|
|
@@ -169,18 +194,6 @@ export default function ${wrapperName}${hasDynamicParams ? '(props: any)' : '()'
|
|
|
169
194
|
navigation[page.section]?.push(entry);
|
|
170
195
|
}
|
|
171
196
|
}
|
|
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
197
|
function dumpNavigation() {
|
|
185
198
|
const navPath = path.join(appDirectory, "navigation.generated.ts");
|
|
186
199
|
const content = `export type MenuEntry = { label: string; path: string; module: string; section: string };
|
|
@@ -354,8 +367,10 @@ function generateDocsPage(moduleConfigs) {
|
|
|
354
367
|
const moduleId = moduleName.replace("@lastbrain/module-", "");
|
|
355
368
|
const docComponentName = `${toPascalCase(moduleId)}ModuleDoc`;
|
|
356
369
|
// Trouver la config du module pour obtenir la description
|
|
357
|
-
const moduleConfig = moduleConfigs.find(mc => mc.moduleName === moduleName);
|
|
358
|
-
const description = moduleConfig
|
|
370
|
+
const moduleConfig = moduleConfigs.find((mc) => mc.moduleName === moduleName);
|
|
371
|
+
const description = moduleConfig
|
|
372
|
+
? getModuleDescription(moduleConfig)
|
|
373
|
+
: "Module non configuré";
|
|
359
374
|
// Importer le composant Doc seulement pour les modules actifs
|
|
360
375
|
if (moduleEntry.active) {
|
|
361
376
|
docImports.push(`import { ${docComponentName} } from "${moduleName}";`);
|
|
@@ -371,18 +386,18 @@ function generateDocsPage(moduleConfigs) {
|
|
|
371
386
|
id: "${config.id}",
|
|
372
387
|
name: "${config.name}",
|
|
373
388
|
description: "${config.description}",
|
|
374
|
-
${config.active ? `content: <${config.component} />,` :
|
|
389
|
+
${config.active ? `content: <${config.component} />,` : "content: null,"}
|
|
375
390
|
available: ${config.active},
|
|
376
391
|
}`);
|
|
377
392
|
});
|
|
378
393
|
const docsContent = `// Auto-generated docs page with module documentation
|
|
379
394
|
import React from "react";
|
|
380
395
|
import { DocPage } from "@lastbrain/app";
|
|
381
|
-
${docImports.join(
|
|
396
|
+
${docImports.join("\n")}
|
|
382
397
|
|
|
383
398
|
export default function DocsPage() {
|
|
384
399
|
const modules = [
|
|
385
|
-
${moduleConfigurations.join(
|
|
400
|
+
${moduleConfigurations.join(",\n")}
|
|
386
401
|
];
|
|
387
402
|
|
|
388
403
|
return <DocPage modules={modules} />;
|
|
@@ -391,6 +406,33 @@ ${moduleConfigurations.join(',\n')}
|
|
|
391
406
|
fs.writeFileSync(docsPagePath, docsContent);
|
|
392
407
|
console.log(`📚 Generated docs page: ${docsPagePath}`);
|
|
393
408
|
}
|
|
409
|
+
function buildGroupedApi(apis, routePath) {
|
|
410
|
+
const segments = routePath.replace(/^\/+/, "").split("/").filter(Boolean);
|
|
411
|
+
const sanitizedSegments = segments[0] === "api" ? segments.slice(1) : segments;
|
|
412
|
+
const routeDir = path.join(appDirectory, "api", ...sanitizedSegments);
|
|
413
|
+
const filePath = path.join(routeDir, "route.ts");
|
|
414
|
+
ensureDirectory(routeDir);
|
|
415
|
+
// Grouper par module/entryPoint pour créer les exports
|
|
416
|
+
const exportsBySource = new Map();
|
|
417
|
+
apis.forEach(({ moduleConfig, api }) => {
|
|
418
|
+
const handler = `${moduleConfig.moduleName}/${api.entryPoint}`;
|
|
419
|
+
if (!exportsBySource.has(handler)) {
|
|
420
|
+
exportsBySource.set(handler, []);
|
|
421
|
+
}
|
|
422
|
+
exportsBySource.get(handler).push(api.handlerExport);
|
|
423
|
+
});
|
|
424
|
+
// Générer les exports - un export statement par source
|
|
425
|
+
const exportStatements = [];
|
|
426
|
+
exportsBySource.forEach((exports, source) => {
|
|
427
|
+
exportStatements.push(`export { ${exports.join(", ")} } from "${source}";`);
|
|
428
|
+
});
|
|
429
|
+
const content = exportStatements.join("\n") + "\n";
|
|
430
|
+
// Ajouter le marqueur de génération au début
|
|
431
|
+
const contentWithMarker = `// GENERATED BY LASTBRAIN MODULE BUILD
|
|
432
|
+
${content}`;
|
|
433
|
+
fs.writeFileSync(filePath, contentWithMarker);
|
|
434
|
+
console.log(`🔌 Generated API route: ${filePath}`);
|
|
435
|
+
}
|
|
394
436
|
function getModuleDescription(moduleConfig) {
|
|
395
437
|
// Essayer de déduire la description depuis les pages ou retourner une description par défaut
|
|
396
438
|
if (moduleConfig.pages.length > 0) {
|
|
@@ -398,16 +440,229 @@ function getModuleDescription(moduleConfig) {
|
|
|
398
440
|
}
|
|
399
441
|
return "Module documentation";
|
|
400
442
|
}
|
|
443
|
+
function cleanGeneratedFiles() {
|
|
444
|
+
const generatedComment = "// GENERATED BY LASTBRAIN MODULE BUILD";
|
|
445
|
+
// Fichiers de base à préserver (paths relatifs exacts)
|
|
446
|
+
const protectedFiles = new Set([
|
|
447
|
+
// API de base
|
|
448
|
+
"api/storage",
|
|
449
|
+
// Layouts de base
|
|
450
|
+
"layout.tsx",
|
|
451
|
+
"not-found.tsx",
|
|
452
|
+
"page.tsx", // Page racine seulement
|
|
453
|
+
"admin/page.tsx", // Page admin racine
|
|
454
|
+
"admin/layout.tsx", // Layout admin racine
|
|
455
|
+
"docs/page.tsx", // Page docs générée
|
|
456
|
+
// Middleware et autres fichiers core
|
|
457
|
+
"middleware.ts",
|
|
458
|
+
// Dossiers de lib et config
|
|
459
|
+
"lib",
|
|
460
|
+
"config"
|
|
461
|
+
]);
|
|
462
|
+
// Fonction pour vérifier si un chemin est protégé
|
|
463
|
+
const isProtected = (filePath) => {
|
|
464
|
+
const relativePath = path.relative(appDirectory, filePath);
|
|
465
|
+
// Protection exacte pour certains fichiers
|
|
466
|
+
if (protectedFiles.has(relativePath)) {
|
|
467
|
+
return true;
|
|
468
|
+
}
|
|
469
|
+
// Protection par préfixe pour les dossiers
|
|
470
|
+
return Array.from(protectedFiles).some(protectedPath => (protectedPath.endsWith('/') || ['lib', 'config', 'api/storage'].includes(protectedPath)) &&
|
|
471
|
+
relativePath.startsWith(protectedPath));
|
|
472
|
+
};
|
|
473
|
+
// Fonction pour nettoyer récursivement un dossier
|
|
474
|
+
const cleanDirectory = (dirPath) => {
|
|
475
|
+
if (!fs.existsSync(dirPath))
|
|
476
|
+
return;
|
|
477
|
+
const items = fs.readdirSync(dirPath);
|
|
478
|
+
for (const item of items) {
|
|
479
|
+
const itemPath = path.join(dirPath, item);
|
|
480
|
+
const stat = fs.statSync(itemPath);
|
|
481
|
+
if (stat.isDirectory()) {
|
|
482
|
+
// Nettoyer récursivement le sous-dossier
|
|
483
|
+
cleanDirectory(itemPath);
|
|
484
|
+
// Supprimer le dossier s'il est vide et non protégé
|
|
485
|
+
try {
|
|
486
|
+
if (!isProtected(itemPath) && fs.readdirSync(itemPath).length === 0) {
|
|
487
|
+
fs.rmdirSync(itemPath);
|
|
488
|
+
console.log(`🗑️ Removed empty directory: ${itemPath}`);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
catch (e) {
|
|
492
|
+
// Ignorer les erreurs de suppression de dossiers
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
else if (item.endsWith('.tsx') || item.endsWith('.ts')) {
|
|
496
|
+
// Vérifier si c'est un fichier généré
|
|
497
|
+
if (!isProtected(itemPath)) {
|
|
498
|
+
try {
|
|
499
|
+
const content = fs.readFileSync(itemPath, 'utf-8');
|
|
500
|
+
// Supprimer les fichiers générés ou les wrapper simples de modules
|
|
501
|
+
if (content.includes(generatedComment) ||
|
|
502
|
+
content.includes('from "@lastbrain/module-') ||
|
|
503
|
+
content.includes('export {') && content.includes('} from "@lastbrain/module-')) {
|
|
504
|
+
fs.unlinkSync(itemPath);
|
|
505
|
+
console.log(`🗑️ Cleaned generated file: ${itemPath}`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
catch (e) {
|
|
509
|
+
// Ignorer les erreurs de lecture/suppression
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
console.log(`🔒 Protected file skipped: ${itemPath}`);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
// Nettoyer les dossiers de sections
|
|
519
|
+
const sectionsToClean = ['(public)', 'auth', 'admin', 'api'];
|
|
520
|
+
sectionsToClean.forEach(section => {
|
|
521
|
+
const sectionPath = path.join(appDirectory, section);
|
|
522
|
+
if (fs.existsSync(sectionPath)) {
|
|
523
|
+
cleanDirectory(sectionPath);
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
// Nettoyer les fichiers générés à la racine
|
|
527
|
+
const rootFiles = ['navigation.generated.ts'];
|
|
528
|
+
rootFiles.forEach(file => {
|
|
529
|
+
const filePath = path.join(appDirectory, file);
|
|
530
|
+
if (fs.existsSync(filePath)) {
|
|
531
|
+
fs.unlinkSync(filePath);
|
|
532
|
+
console.log(`🗑️ Cleaned root file: ${filePath}`);
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
console.log("🧹 Cleanup completed");
|
|
536
|
+
}
|
|
537
|
+
function generateAppAside() {
|
|
538
|
+
const targetPath = path.join(appDirectory, "components", "AppAside.tsx");
|
|
539
|
+
// Ne pas écraser si le fichier existe déjà
|
|
540
|
+
if (fs.existsSync(targetPath)) {
|
|
541
|
+
console.log(`⏭️ AppAside already exists, skipping: ${targetPath}`);
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
const templateContent = `"use client";
|
|
545
|
+
|
|
546
|
+
import { AppAside as UIAppAside } from "@lastbrain/app";
|
|
547
|
+
import { useAuthSession } from "@lastbrain/app";
|
|
548
|
+
import { menuConfig } from "../../config/menu";
|
|
549
|
+
|
|
550
|
+
interface AppAsideProps {
|
|
551
|
+
className?: string;
|
|
552
|
+
isVisible?: boolean;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
export function AppAside({ className = "", isVisible = true }: AppAsideProps) {
|
|
556
|
+
const { isSuperAdmin } = useAuthSession();
|
|
557
|
+
|
|
558
|
+
return (
|
|
559
|
+
<UIAppAside
|
|
560
|
+
className={className}
|
|
561
|
+
menuConfig={menuConfig}
|
|
562
|
+
isSuperAdmin={isSuperAdmin}
|
|
563
|
+
isVisible={isVisible}
|
|
564
|
+
/>
|
|
565
|
+
);
|
|
566
|
+
}`;
|
|
567
|
+
try {
|
|
568
|
+
ensureDirectory(path.dirname(targetPath));
|
|
569
|
+
fs.writeFileSync(targetPath, templateContent, "utf-8");
|
|
570
|
+
console.log(`✅ Generated AppAside component: ${targetPath}`);
|
|
571
|
+
}
|
|
572
|
+
catch (error) {
|
|
573
|
+
console.error(`❌ Error generating AppAside component: ${error}`);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
function generateLayouts() {
|
|
577
|
+
// Générer layout auth avec sidebar
|
|
578
|
+
const authLayoutPath = path.join(appDirectory, "auth", "layout.tsx");
|
|
579
|
+
if (!fs.existsSync(authLayoutPath)) {
|
|
580
|
+
const authLayoutContent = `import { AuthLayoutWithSidebar } from "@lastbrain/app";
|
|
581
|
+
import { menuConfig } from "../../config/menu";
|
|
582
|
+
|
|
583
|
+
export default function SectionLayout({
|
|
584
|
+
children,
|
|
585
|
+
}: {
|
|
586
|
+
children: React.ReactNode;
|
|
587
|
+
}) {
|
|
588
|
+
return (
|
|
589
|
+
<AuthLayoutWithSidebar menuConfig={menuConfig}>
|
|
590
|
+
{children}
|
|
591
|
+
</AuthLayoutWithSidebar>
|
|
592
|
+
);
|
|
593
|
+
}`;
|
|
594
|
+
try {
|
|
595
|
+
ensureDirectory(path.dirname(authLayoutPath));
|
|
596
|
+
fs.writeFileSync(authLayoutPath, authLayoutContent, "utf-8");
|
|
597
|
+
console.log(`✅ Generated auth layout with sidebar: ${authLayoutPath}`);
|
|
598
|
+
}
|
|
599
|
+
catch (error) {
|
|
600
|
+
console.error(`❌ Error generating auth layout: ${error}`);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
else {
|
|
604
|
+
console.log(`⏭️ Auth layout already exists, skipping: ${authLayoutPath}`);
|
|
605
|
+
}
|
|
606
|
+
// Générer layout admin avec sidebar
|
|
607
|
+
const adminLayoutPath = path.join(appDirectory, "admin", "layout.tsx");
|
|
608
|
+
if (!fs.existsSync(adminLayoutPath)) {
|
|
609
|
+
const adminLayoutContent = `import { AdminLayoutWithSidebar } from "@lastbrain/app";
|
|
610
|
+
import { menuConfig } from "../../config/menu";
|
|
611
|
+
|
|
612
|
+
export default function AdminLayout({
|
|
613
|
+
children,
|
|
614
|
+
}: {
|
|
615
|
+
children: React.ReactNode;
|
|
616
|
+
}) {
|
|
617
|
+
return (
|
|
618
|
+
<AdminLayoutWithSidebar menuConfig={menuConfig}>
|
|
619
|
+
{children}
|
|
620
|
+
</AdminLayoutWithSidebar>
|
|
621
|
+
);
|
|
622
|
+
}`;
|
|
623
|
+
try {
|
|
624
|
+
ensureDirectory(path.dirname(adminLayoutPath));
|
|
625
|
+
fs.writeFileSync(adminLayoutPath, adminLayoutContent, "utf-8");
|
|
626
|
+
console.log(`✅ Generated admin layout with sidebar: ${adminLayoutPath}`);
|
|
627
|
+
}
|
|
628
|
+
catch (error) {
|
|
629
|
+
console.error(`❌ Error generating admin layout: ${error}`);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
else {
|
|
633
|
+
console.log(`⏭️ Admin layout already exists, skipping: ${adminLayoutPath}`);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
401
636
|
export async function runModuleBuild() {
|
|
402
637
|
ensureDirectory(appDirectory);
|
|
638
|
+
// Nettoyer les fichiers générés précédemment
|
|
639
|
+
console.log("🧹 Cleaning previously generated files...");
|
|
640
|
+
cleanGeneratedFiles();
|
|
403
641
|
const moduleConfigs = await loadModuleConfigs();
|
|
642
|
+
console.log(`🔍 Loaded ${moduleConfigs.length} module configurations`);
|
|
643
|
+
// Générer les pages
|
|
404
644
|
moduleConfigs.forEach((moduleConfig) => {
|
|
645
|
+
console.log(`📦 Processing module: ${moduleConfig.moduleName} with ${moduleConfig.pages.length} pages`);
|
|
405
646
|
moduleConfig.pages.forEach((page) => buildPage(moduleConfig, page));
|
|
406
|
-
|
|
647
|
+
});
|
|
648
|
+
// Grouper les APIs par chemin pour éviter les écrasements de fichier
|
|
649
|
+
const apisByPath = new Map();
|
|
650
|
+
moduleConfigs.forEach((moduleConfig) => {
|
|
651
|
+
moduleConfig.apis.forEach((api) => {
|
|
652
|
+
if (!apisByPath.has(api.path)) {
|
|
653
|
+
apisByPath.set(api.path, []);
|
|
654
|
+
}
|
|
655
|
+
apisByPath.get(api.path).push({ moduleConfig, api });
|
|
656
|
+
});
|
|
657
|
+
});
|
|
658
|
+
// Générer les fichiers de route groupés
|
|
659
|
+
apisByPath.forEach((apis, routePath) => {
|
|
660
|
+
buildGroupedApi(apis, routePath);
|
|
407
661
|
});
|
|
408
662
|
dumpNavigation();
|
|
409
663
|
generateMenuConfig(moduleConfigs);
|
|
410
664
|
generateDocsPage(moduleConfigs);
|
|
665
|
+
generateAppAside();
|
|
666
|
+
generateLayouts();
|
|
411
667
|
copyModuleMigrations(moduleConfigs);
|
|
412
668
|
}
|
|
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
|
}
|
|
@@ -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()}`));
|
|
@@ -278,5 +282,14 @@ export async function removeModule(moduleName, targetDir) {
|
|
|
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 (error) {
|
|
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
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"script-runner.d.ts","sourceRoot":"","sources":["../../src/scripts/script-runner.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,wBAAsB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,EAAO,iBAqBtE"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import path from "path";
|
|
3
|
+
/**
|
|
4
|
+
* Exécute un script via node en ajustant le chemin
|
|
5
|
+
*/
|
|
6
|
+
export async function runScript(scriptName, args = []) {
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
const scriptPath = path.join(__dirname, `${scriptName}.js`);
|
|
9
|
+
const child = spawn("node", [scriptPath, ...args], {
|
|
10
|
+
stdio: "inherit",
|
|
11
|
+
env: { ...process.env, PROJECT_ROOT: process.cwd() },
|
|
12
|
+
});
|
|
13
|
+
child.on("close", (code) => {
|
|
14
|
+
if (code === 0) {
|
|
15
|
+
resolve();
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
reject(new Error(`Script ${scriptName} exited with code ${code}`));
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
child.on("error", (error) => {
|
|
22
|
+
reject(error);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
}
|