@lastbrain/app 0.1.36 → 0.1.39
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.js +5 -16
- package/dist/layouts/AdminLayoutWithSidebar.d.ts.map +1 -1
- package/dist/layouts/AdminLayoutWithSidebar.js +36 -1
- package/dist/layouts/AuthLayoutWithSidebar.d.ts.map +1 -1
- package/dist/layouts/AuthLayoutWithSidebar.js +36 -1
- package/dist/layouts/PublicLayout.js +1 -1
- package/dist/layouts/PublicLayoutWithSidebar.d.ts.map +1 -1
- package/dist/layouts/PublicLayoutWithSidebar.js +36 -1
- package/dist/scripts/init-app.d.ts.map +1 -1
- package/dist/scripts/init-app.js +2 -2
- package/dist/scripts/module-add.d.ts +0 -11
- package/dist/scripts/module-add.d.ts.map +1 -1
- package/dist/scripts/module-add.js +45 -22
- package/dist/scripts/module-build.d.ts.map +1 -1
- package/dist/scripts/module-build.js +90 -1
- package/dist/scripts/module-create.d.ts +23 -0
- package/dist/scripts/module-create.d.ts.map +1 -1
- package/dist/scripts/module-create.js +289 -56
- package/dist/scripts/module-delete.d.ts +6 -0
- package/dist/scripts/module-delete.d.ts.map +1 -0
- package/dist/scripts/module-delete.js +143 -0
- package/dist/scripts/module-list.d.ts.map +1 -1
- package/dist/scripts/module-list.js +2 -2
- package/dist/scripts/module-remove.d.ts.map +1 -1
- package/dist/scripts/module-remove.js +20 -4
- package/dist/styles.css +1 -1
- package/dist/templates/DefaultDoc.d.ts.map +1 -1
- package/dist/templates/DefaultDoc.js +132 -9
- package/dist/templates/DocPage.d.ts.map +1 -1
- package/dist/templates/DocPage.js +24 -7
- package/package.json +4 -4
- package/src/__tests__/module-registry.test.ts +5 -17
- package/src/layouts/AdminLayoutWithSidebar.tsx +51 -1
- package/src/layouts/AuthLayoutWithSidebar.tsx +51 -1
- package/src/layouts/PublicLayout.tsx +1 -1
- package/src/layouts/PublicLayoutWithSidebar.tsx +51 -1
- package/src/scripts/init-app.ts +5 -2
- package/src/scripts/module-add.ts +55 -23
- package/src/scripts/module-build.ts +109 -1
- package/src/scripts/module-create.ts +392 -69
- package/src/scripts/module-delete.ts +202 -0
- package/src/scripts/module-list.ts +9 -2
- package/src/scripts/module-remove.ts +36 -4
- package/src/templates/DefaultDoc.tsx +1149 -424
- package/src/templates/DocPage.tsx +26 -10
|
@@ -2,6 +2,18 @@ import fs from "fs-extra";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import inquirer from "inquirer";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
// Noms réservés pour éviter les collisions avec le routeur/app
|
|
9
|
+
const RESERVED_PAGE_NAMES = new Set([
|
|
10
|
+
"layout",
|
|
11
|
+
"page",
|
|
12
|
+
"api",
|
|
13
|
+
"admin",
|
|
14
|
+
"auth",
|
|
15
|
+
"public",
|
|
16
|
+
]);
|
|
5
17
|
/**
|
|
6
18
|
* Parse une chaîne de pages séparées par des virgules
|
|
7
19
|
* Ex: "legal, privacy, terms" => ["legal", "privacy", "terms"]
|
|
@@ -14,6 +26,18 @@ function parsePagesList(input) {
|
|
|
14
26
|
.map((p) => p.trim())
|
|
15
27
|
.filter((p) => p.length > 0);
|
|
16
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Slugifie un nom de page: minuscules, tirets, alphanum uniquement
|
|
31
|
+
*/
|
|
32
|
+
function slugifyPageName(name) {
|
|
33
|
+
return name
|
|
34
|
+
.trim()
|
|
35
|
+
.toLowerCase()
|
|
36
|
+
.replace(/[\s_]+/g, "-")
|
|
37
|
+
.replace(/[^a-z0-9-]/g, "")
|
|
38
|
+
.replace(/--+/g, "-")
|
|
39
|
+
.replace(/^-+|-+$/g, "");
|
|
40
|
+
}
|
|
17
41
|
/**
|
|
18
42
|
* Parse une chaîne de tables séparées par des virgules
|
|
19
43
|
* Ex: "settings, users" => ["settings", "users"]
|
|
@@ -51,7 +75,8 @@ function getLastBrainPackageVersions(rootDir) {
|
|
|
51
75
|
*/
|
|
52
76
|
function generatePackageJson(moduleName, slug, rootDir) {
|
|
53
77
|
const versions = getLastBrainPackageVersions(rootDir);
|
|
54
|
-
const
|
|
78
|
+
const moduleNameOnly = slug.replace("module-", "");
|
|
79
|
+
const buildConfigExport = `./${moduleNameOnly}.build.config`;
|
|
55
80
|
return JSON.stringify({
|
|
56
81
|
name: moduleName,
|
|
57
82
|
version: "0.1.0",
|
|
@@ -84,8 +109,8 @@ function generatePackageJson(moduleName, slug, rootDir) {
|
|
|
84
109
|
default: "./dist/server.js",
|
|
85
110
|
},
|
|
86
111
|
[buildConfigExport]: {
|
|
87
|
-
types: `./dist/${
|
|
88
|
-
default: `./dist/${
|
|
112
|
+
types: `./dist/${moduleNameOnly}.build.config.d.ts`,
|
|
113
|
+
default: `./dist/${moduleNameOnly}.build.config.js`,
|
|
89
114
|
},
|
|
90
115
|
"./api/*": {
|
|
91
116
|
types: "./dist/api/*.d.ts",
|
|
@@ -114,6 +139,38 @@ function generateTsConfig() {
|
|
|
114
139
|
*/
|
|
115
140
|
function generateBuildConfig(config) {
|
|
116
141
|
const { moduleName, pages, tables } = config;
|
|
142
|
+
const moduleNameOnly = config.slug.replace("module-", "");
|
|
143
|
+
// Helper pour normaliser les chemins (évite // et trim)
|
|
144
|
+
function normalizePath(...segments) {
|
|
145
|
+
return ("/" +
|
|
146
|
+
segments
|
|
147
|
+
.map((s) => s.replace(/^\/+/g, "").replace(/\/+$/g, ""))
|
|
148
|
+
.filter(Boolean)
|
|
149
|
+
.join("/"));
|
|
150
|
+
}
|
|
151
|
+
// Construit un path de menu selon la section en respectant le pattern attendu
|
|
152
|
+
function buildMenuPath(section, pagePath, pageName) {
|
|
153
|
+
const cleanedPagePath = pagePath.replace(/^\/+/g, "");
|
|
154
|
+
switch (section) {
|
|
155
|
+
case "public": {
|
|
156
|
+
// Public: /<module>/<page> (si non déjà préfixé)
|
|
157
|
+
if (cleanedPagePath.startsWith(moduleNameOnly + "/")) {
|
|
158
|
+
return normalizePath(cleanedPagePath); // déjà préfixé
|
|
159
|
+
}
|
|
160
|
+
return normalizePath(moduleNameOnly, cleanedPagePath);
|
|
161
|
+
}
|
|
162
|
+
case "auth": {
|
|
163
|
+
// Auth: /auth/<module>/<page>
|
|
164
|
+
return normalizePath("auth", moduleNameOnly, cleanedPagePath);
|
|
165
|
+
}
|
|
166
|
+
case "admin": {
|
|
167
|
+
// Admin: /admin/<module>/<pageName> (utilise le slug brut)
|
|
168
|
+
return normalizePath("admin", moduleNameOnly, pageName);
|
|
169
|
+
}
|
|
170
|
+
default:
|
|
171
|
+
return normalizePath(cleanedPagePath);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
117
174
|
// Générer la liste des pages
|
|
118
175
|
const pagesConfig = pages.map((page) => {
|
|
119
176
|
const componentName = page.name
|
|
@@ -152,11 +209,12 @@ function generateBuildConfig(config) {
|
|
|
152
209
|
.split("-")
|
|
153
210
|
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
154
211
|
.join(" ");
|
|
212
|
+
const menuPath = buildMenuPath("public", page.path, page.name);
|
|
155
213
|
return ` {
|
|
156
214
|
title: "${title}",
|
|
157
215
|
description: "Page ${title}",
|
|
158
216
|
icon: "FileText",
|
|
159
|
-
path: "${
|
|
217
|
+
path: "${menuPath}",
|
|
160
218
|
order: ${index + 1},
|
|
161
219
|
}`;
|
|
162
220
|
});
|
|
@@ -170,11 +228,12 @@ function generateBuildConfig(config) {
|
|
|
170
228
|
.split("-")
|
|
171
229
|
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
172
230
|
.join(" ");
|
|
231
|
+
const menuPath = buildMenuPath("auth", page.path, page.name);
|
|
173
232
|
return ` {
|
|
174
233
|
title: "${title}",
|
|
175
234
|
description: "Page ${title}",
|
|
176
235
|
icon: "FileText",
|
|
177
|
-
path: "${
|
|
236
|
+
path: "${menuPath}",
|
|
178
237
|
order: ${index + 1},
|
|
179
238
|
}`;
|
|
180
239
|
});
|
|
@@ -188,11 +247,12 @@ function generateBuildConfig(config) {
|
|
|
188
247
|
.split("-")
|
|
189
248
|
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
190
249
|
.join(" ");
|
|
250
|
+
const menuPath = buildMenuPath("admin", page.path, page.name);
|
|
191
251
|
return ` {
|
|
192
252
|
title: "${title}",
|
|
193
253
|
description: "Page ${title}",
|
|
194
254
|
icon: "Settings",
|
|
195
|
-
path: "${
|
|
255
|
+
path: "${menuPath}",
|
|
196
256
|
order: ${index + 1},
|
|
197
257
|
}`;
|
|
198
258
|
});
|
|
@@ -233,26 +293,29 @@ export default buildConfig;
|
|
|
233
293
|
/**
|
|
234
294
|
* Génère le contenu du fichier index.ts
|
|
235
295
|
*/
|
|
236
|
-
function
|
|
296
|
+
function toPascalCase(value) {
|
|
297
|
+
return value
|
|
298
|
+
.split(/[-_]/g)
|
|
299
|
+
.filter(Boolean)
|
|
300
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
301
|
+
.join("");
|
|
302
|
+
}
|
|
303
|
+
function generateIndexTs(pages, moduleNameOnly) {
|
|
237
304
|
const exports = pages.map((page) => {
|
|
238
|
-
const componentName = page.name
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
.join("");
|
|
242
|
-
const fileName = page.name
|
|
243
|
-
.split("-")
|
|
244
|
-
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
245
|
-
.join("");
|
|
246
|
-
return `export { ${componentName}Page } from "./web/${page.section}/${fileName}Page.js";`;
|
|
305
|
+
const componentName = toPascalCase(page.name);
|
|
306
|
+
const fileName = toPascalCase(page.name);
|
|
307
|
+
return `export { ${componentName}Page } from "./web/${page.section}/${fileName}Page";`;
|
|
247
308
|
});
|
|
309
|
+
const moduleAlias = toPascalCase(moduleNameOnly);
|
|
248
310
|
return `// Client Components
|
|
249
311
|
${exports.join("\n")}
|
|
250
312
|
|
|
251
|
-
// Documentation Component
|
|
252
|
-
export { Doc } from "./components/Doc
|
|
313
|
+
// Documentation Component
|
|
314
|
+
export { Doc } from "./components/Doc";
|
|
315
|
+
export { Doc as ${moduleAlias}ModuleDoc } from "./components/Doc";
|
|
253
316
|
|
|
254
317
|
// Configuration de build
|
|
255
|
-
export { default as buildConfig } from "
|
|
318
|
+
export { default as buildConfig } from "./${moduleNameOnly}.build.config";
|
|
256
319
|
`;
|
|
257
320
|
}
|
|
258
321
|
/**
|
|
@@ -304,7 +367,13 @@ export function ${componentName}Page() {
|
|
|
304
367
|
*/
|
|
305
368
|
function generateApiRoute(tableName, section) {
|
|
306
369
|
const authRequired = section !== "public";
|
|
307
|
-
|
|
370
|
+
const clientImport = section === "admin"
|
|
371
|
+
? 'import { getSupabaseServiceClient } from "@lastbrain/core/server"'
|
|
372
|
+
: 'import { getSupabaseServerClient } from "@lastbrain/core/server"';
|
|
373
|
+
const clientGetter = section === "admin"
|
|
374
|
+
? "getSupabaseServiceClient"
|
|
375
|
+
: "getSupabaseServerClient";
|
|
376
|
+
return `${clientImport};
|
|
308
377
|
|
|
309
378
|
const jsonResponse = (payload: unknown, status = 200) => {
|
|
310
379
|
return new Response(JSON.stringify(payload), {
|
|
@@ -319,7 +388,7 @@ const jsonResponse = (payload: unknown, status = 200) => {
|
|
|
319
388
|
* GET - Liste tous les enregistrements de ${tableName}
|
|
320
389
|
*/
|
|
321
390
|
export async function GET(request: Request) {
|
|
322
|
-
const supabase = await
|
|
391
|
+
const supabase = await ${clientGetter}();
|
|
323
392
|
${authRequired
|
|
324
393
|
? `
|
|
325
394
|
const { data: { user }, error: authError } = await supabase.auth.getUser();
|
|
@@ -344,7 +413,7 @@ export async function GET(request: Request) {
|
|
|
344
413
|
* POST - Crée un nouvel enregistrement dans ${tableName}
|
|
345
414
|
*/
|
|
346
415
|
export async function POST(request: Request) {
|
|
347
|
-
const supabase = await
|
|
416
|
+
const supabase = await ${clientGetter}();
|
|
348
417
|
${authRequired
|
|
349
418
|
? `
|
|
350
419
|
const { data: { user }, error: authError } = await supabase.auth.getUser();
|
|
@@ -356,9 +425,12 @@ export async function POST(request: Request) {
|
|
|
356
425
|
|
|
357
426
|
const body = await request.json();
|
|
358
427
|
|
|
428
|
+
// Injection côté serveur de owner_id si l'utilisateur est authentifié (sécurité)
|
|
429
|
+
const insertPayload = ${authRequired ? `{ ...body, owner_id: user.id }` : "body"};
|
|
430
|
+
|
|
359
431
|
const { data, error } = await supabase
|
|
360
432
|
.from("${tableName}")
|
|
361
|
-
.insert(
|
|
433
|
+
.insert(insertPayload)
|
|
362
434
|
.select()
|
|
363
435
|
.single();
|
|
364
436
|
|
|
@@ -373,7 +445,7 @@ export async function POST(request: Request) {
|
|
|
373
445
|
* PUT - Met à jour un enregistrement dans ${tableName}
|
|
374
446
|
*/
|
|
375
447
|
export async function PUT(request: Request) {
|
|
376
|
-
const supabase = await
|
|
448
|
+
const supabase = await ${clientGetter}();
|
|
377
449
|
${authRequired
|
|
378
450
|
? `
|
|
379
451
|
const { data: { user }, error: authError } = await supabase.auth.getUser();
|
|
@@ -408,7 +480,7 @@ export async function PUT(request: Request) {
|
|
|
408
480
|
* DELETE - Supprime un enregistrement de ${tableName}
|
|
409
481
|
*/
|
|
410
482
|
export async function DELETE(request: Request) {
|
|
411
|
-
const supabase = await
|
|
483
|
+
const supabase = await ${clientGetter}();
|
|
412
484
|
${authRequired
|
|
413
485
|
? `
|
|
414
486
|
const { data: { user }, error: authError } = await supabase.auth.getUser();
|
|
@@ -528,6 +600,7 @@ ${tablesSQL}
|
|
|
528
600
|
*/
|
|
529
601
|
function generateDocComponent(config) {
|
|
530
602
|
const moduleNameClean = config.slug.replace("module-", "");
|
|
603
|
+
const moduleNameOnly = moduleNameClean;
|
|
531
604
|
// Generate pages sections
|
|
532
605
|
const publicPages = config.pages.filter((p) => p.section === "public");
|
|
533
606
|
const authPages = config.pages.filter((p) => p.section === "auth");
|
|
@@ -598,7 +671,7 @@ function generateDocComponent(config) {
|
|
|
598
671
|
pagesSection += `
|
|
599
672
|
<div className="flex items-start gap-2">
|
|
600
673
|
<Chip size="sm" color="secondary" variant="flat">GET</Chip>
|
|
601
|
-
<code className="text-sm"
|
|
674
|
+
<code className="text-sm">/admin/${moduleNameOnly}/${page.name}</code>
|
|
602
675
|
<span className="text-sm text-slate-600 dark:text-slate-400">- ${componentName}</span>
|
|
603
676
|
</div>`;
|
|
604
677
|
}
|
|
@@ -878,7 +951,7 @@ async function generateModuleReadme(config, moduleDir) {
|
|
|
878
951
|
.split("-")
|
|
879
952
|
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
880
953
|
.join("");
|
|
881
|
-
md += `- **GET**
|
|
954
|
+
md += `- **GET** \`/admin/${config.slug.replace("module-", "")}/${page.name}\` - ${componentName}\n`;
|
|
882
955
|
}
|
|
883
956
|
md += `\n`;
|
|
884
957
|
}
|
|
@@ -965,10 +1038,116 @@ async function generateModuleReadme(config, moduleDir) {
|
|
|
965
1038
|
await fs.writeFile(readmePath, md);
|
|
966
1039
|
console.log(chalk.yellow(" 📄 README.md"));
|
|
967
1040
|
}
|
|
1041
|
+
/**
|
|
1042
|
+
* Met à jour le registre des modules dans core/src/config/modules.ts
|
|
1043
|
+
*/
|
|
1044
|
+
async function updateModuleRegistry(config, rootDir) {
|
|
1045
|
+
const moduleRegistryPath = path.join(rootDir, "packages", "core", "src", "config", "modules.ts");
|
|
1046
|
+
if (!fs.existsSync(moduleRegistryPath)) {
|
|
1047
|
+
console.log(chalk.yellow(" ⚠️ Fichier de registre non trouvé, création..."));
|
|
1048
|
+
// Si le fichier n'existe pas, on le crée avec le module actuel
|
|
1049
|
+
const content = `/**
|
|
1050
|
+
* Configuration centralisée des modules LastBrain
|
|
1051
|
+
* Ce fichier est auto-généré et maintenu par les scripts de gestion des modules
|
|
1052
|
+
*/
|
|
1053
|
+
|
|
1054
|
+
export interface ModuleMetadata {
|
|
1055
|
+
name: string;
|
|
1056
|
+
package: string;
|
|
1057
|
+
description: string;
|
|
1058
|
+
emoji: string;
|
|
1059
|
+
version?: string;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
export const AVAILABLE_MODULES: ModuleMetadata[] = [
|
|
1063
|
+
{
|
|
1064
|
+
name: "${config.slug.replace("module-", "")}",
|
|
1065
|
+
package: "@lastbrain/${config.slug}",
|
|
1066
|
+
description: "Module ${config.moduleName}",
|
|
1067
|
+
emoji: "📦",
|
|
1068
|
+
},
|
|
1069
|
+
];
|
|
1070
|
+
|
|
1071
|
+
/**
|
|
1072
|
+
* Récupère les métadonnées d'un module par son nom
|
|
1073
|
+
*/
|
|
1074
|
+
export function getModuleMetadata(name: string): ModuleMetadata | undefined {
|
|
1075
|
+
return AVAILABLE_MODULES.find((m) => m.name === name);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
/**
|
|
1079
|
+
* Vérifie si un module existe
|
|
1080
|
+
*/
|
|
1081
|
+
export function moduleExists(name: string): boolean {
|
|
1082
|
+
return AVAILABLE_MODULES.some((m) => m.name === name);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
/**
|
|
1086
|
+
* Récupère la liste des noms de modules disponibles
|
|
1087
|
+
*/
|
|
1088
|
+
export function getAvailableModuleNames(): string[] {
|
|
1089
|
+
return AVAILABLE_MODULES.map((m) => m.name);
|
|
1090
|
+
}
|
|
1091
|
+
`;
|
|
1092
|
+
await fs.writeFile(moduleRegistryPath, content, "utf-8");
|
|
1093
|
+
console.log(chalk.green(" ✓ Registre créé"));
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
try {
|
|
1097
|
+
let content = await fs.readFile(moduleRegistryPath, "utf-8");
|
|
1098
|
+
const moduleName = config.slug.replace("module-", "");
|
|
1099
|
+
const moduleEntry = ` {
|
|
1100
|
+
name: "${moduleName}",
|
|
1101
|
+
package: "@lastbrain/${config.slug}",
|
|
1102
|
+
description: "Module ${config.moduleName}",
|
|
1103
|
+
emoji: "📦",
|
|
1104
|
+
},`;
|
|
1105
|
+
// Vérifier si le module existe déjà
|
|
1106
|
+
if (content.includes(`name: "${moduleName}"`)) {
|
|
1107
|
+
console.log(chalk.yellow(` ⚠️ Module ${moduleName} déjà présent dans le registre`));
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
// Trouver le tableau AVAILABLE_MODULES et ajouter le module
|
|
1111
|
+
const arrayMatch = content.match(/export const AVAILABLE_MODULES: ModuleMetadata\[\] = \[([\s\S]*?)\];/);
|
|
1112
|
+
if (arrayMatch) {
|
|
1113
|
+
const arrayContent = arrayMatch[1];
|
|
1114
|
+
const lastItem = arrayContent.trim().split("\n").pop();
|
|
1115
|
+
// Ajouter le nouveau module à la fin du tableau
|
|
1116
|
+
const newArrayContent = arrayContent.trimEnd() + "\n" + moduleEntry + "\n";
|
|
1117
|
+
content = content.replace(/export const AVAILABLE_MODULES: ModuleMetadata\[\] = \[([\s\S]*?)\];/, `export const AVAILABLE_MODULES: ModuleMetadata[] = [${newArrayContent}];`);
|
|
1118
|
+
await fs.writeFile(moduleRegistryPath, content, "utf-8");
|
|
1119
|
+
console.log(chalk.green(` ✓ Module ${moduleName} ajouté au registre`));
|
|
1120
|
+
}
|
|
1121
|
+
else {
|
|
1122
|
+
console.log(chalk.yellow(" ⚠️ Format du registre non reconnu, ajout manuel requis"));
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
catch (error) {
|
|
1126
|
+
console.log(chalk.yellow(` ⚠️ Erreur lors de la mise à jour du registre: ${error}`));
|
|
1127
|
+
console.log(chalk.gray(" Vous devrez ajouter manuellement le module au registre"));
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
/**
|
|
1131
|
+
* Trouve le répertoire racine du workspace (avec pnpm-workspace.yaml)
|
|
1132
|
+
*/
|
|
1133
|
+
export function findWorkspaceRoot() {
|
|
1134
|
+
let rootDir = process.cwd();
|
|
1135
|
+
let attempts = 0;
|
|
1136
|
+
const maxAttempts = 5;
|
|
1137
|
+
while (attempts < maxAttempts) {
|
|
1138
|
+
const workspaceFile = path.join(rootDir, "pnpm-workspace.yaml");
|
|
1139
|
+
if (fs.existsSync(workspaceFile)) {
|
|
1140
|
+
return rootDir;
|
|
1141
|
+
}
|
|
1142
|
+
rootDir = path.resolve(rootDir, "..");
|
|
1143
|
+
attempts++;
|
|
1144
|
+
}
|
|
1145
|
+
throw new Error("Impossible de trouver le répertoire racine du workspace (pnpm-workspace.yaml non trouvé)");
|
|
1146
|
+
}
|
|
968
1147
|
/**
|
|
969
1148
|
* Crée la structure du module
|
|
970
1149
|
*/
|
|
971
|
-
async function createModuleStructure(config, rootDir) {
|
|
1150
|
+
export async function createModuleStructure(config, rootDir) {
|
|
972
1151
|
const moduleDir = path.join(rootDir, "packages", config.slug);
|
|
973
1152
|
console.log(chalk.blue(`\n📦 Création du module ${config.slug}...\n`));
|
|
974
1153
|
// Créer la structure de base
|
|
@@ -978,22 +1157,23 @@ async function createModuleStructure(config, rootDir) {
|
|
|
978
1157
|
await fs.ensureDir(path.join(moduleDir, "src", "api"));
|
|
979
1158
|
await fs.ensureDir(path.join(moduleDir, "src", "components"));
|
|
980
1159
|
await fs.ensureDir(path.join(moduleDir, "supabase", "migrations"));
|
|
1160
|
+
await fs.ensureDir(path.join(moduleDir, "supabase", "migrations-down"));
|
|
981
1161
|
// Créer package.json
|
|
982
1162
|
console.log(chalk.yellow(" 📄 package.json"));
|
|
983
1163
|
await fs.writeFile(path.join(moduleDir, "package.json"), generatePackageJson(config.moduleName, config.slug, rootDir));
|
|
984
1164
|
// Créer tsconfig.json
|
|
985
1165
|
console.log(chalk.yellow(" 📄 tsconfig.json"));
|
|
986
1166
|
await fs.writeFile(path.join(moduleDir, "tsconfig.json"), generateTsConfig());
|
|
987
|
-
// Créer {
|
|
988
|
-
const
|
|
1167
|
+
// Créer {name}.build.config.ts (sans le préfixe module-)
|
|
1168
|
+
const moduleNameOnly = config.slug.replace("module-", "");
|
|
1169
|
+
const buildConfigFileName = `${moduleNameOnly}.build.config.ts`;
|
|
989
1170
|
console.log(chalk.yellow(` 📄 src/${buildConfigFileName}`));
|
|
990
1171
|
await fs.writeFile(path.join(moduleDir, "src", buildConfigFileName), generateBuildConfig(config));
|
|
991
1172
|
// Créer index.ts
|
|
992
1173
|
console.log(chalk.yellow(" 📄 src/index.ts"));
|
|
993
|
-
await fs.writeFile(path.join(moduleDir, "src", "index.ts"), generateIndexTs(config.pages));
|
|
994
|
-
//
|
|
995
|
-
|
|
996
|
-
await fs.writeFile(path.join(moduleDir, "src", "server.ts"), generateServerTs(config.tables));
|
|
1174
|
+
await fs.writeFile(path.join(moduleDir, "src", "index.ts"), generateIndexTs(config.pages, moduleNameOnly));
|
|
1175
|
+
// Note: server.ts n'est plus généré pour éviter les conflits d'exports
|
|
1176
|
+
// Les routes API sont exposées via le build.config et l'app les importe dynamiquement
|
|
997
1177
|
// Créer Doc.tsx
|
|
998
1178
|
console.log(chalk.yellow(" 📄 src/components/Doc.tsx"));
|
|
999
1179
|
await fs.writeFile(path.join(moduleDir, "src", "components", "Doc.tsx"), generateDocComponent(config));
|
|
@@ -1034,17 +1214,56 @@ async function createModuleStructure(config, rootDir) {
|
|
|
1034
1214
|
const migrationFileName = `${timestamp}_${config.slug}_init.sql`;
|
|
1035
1215
|
console.log(chalk.yellow(` 📄 supabase/migrations/${migrationFileName}`));
|
|
1036
1216
|
await fs.writeFile(path.join(moduleDir, "supabase", "migrations", migrationFileName), generateMigration(config.tables, config.slug));
|
|
1217
|
+
// Génération du fichier DOWN correspondant pour rollback
|
|
1218
|
+
const downFileName = `${timestamp}_${config.slug}_init.sql`;
|
|
1219
|
+
const downContent = config.tables
|
|
1220
|
+
.map((t) => `-- Rollback for table ${t.name}\nDROP POLICY IF EXISTS ${t.name}_owner_delete ON public.${t.name};\nDROP POLICY IF EXISTS ${t.name}_owner_update ON public.${t.name};\nDROP POLICY IF EXISTS ${t.name}_owner_insert ON public.${t.name};\nDROP POLICY IF EXISTS ${t.name}_owner_select ON public.${t.name};\nDROP TRIGGER IF EXISTS set_${t.name}_updated_at ON public.${t.name};\nDROP INDEX IF EXISTS idx_${t.name}_owner_id;\nDROP TABLE IF EXISTS public.${t.name};\n`)
|
|
1221
|
+
.join("\n");
|
|
1222
|
+
console.log(chalk.yellow(` 📄 supabase/migrations-down/${downFileName}`));
|
|
1223
|
+
await fs.writeFile(path.join(moduleDir, "supabase", "migrations-down", downFileName), `-- DOWN migration for ${config.slug}\n${downContent}`);
|
|
1037
1224
|
}
|
|
1038
1225
|
// Générer la documentation du module
|
|
1039
1226
|
console.log(chalk.blue("\n📝 Génération de la documentation..."));
|
|
1040
1227
|
await generateModuleReadme(config, moduleDir);
|
|
1228
|
+
// Installer les dépendances
|
|
1229
|
+
console.log(chalk.blue("\n📦 Installation des dépendances..."));
|
|
1230
|
+
const { execSync } = await import("child_process");
|
|
1231
|
+
const installCmd = process.env.CI
|
|
1232
|
+
? "pnpm install --no-frozen-lockfile"
|
|
1233
|
+
: "pnpm install";
|
|
1234
|
+
try {
|
|
1235
|
+
execSync(installCmd, {
|
|
1236
|
+
cwd: moduleDir,
|
|
1237
|
+
stdio: "inherit",
|
|
1238
|
+
});
|
|
1239
|
+
console.log(chalk.green("\n✅ Dépendances installées avec succès!"));
|
|
1240
|
+
}
|
|
1241
|
+
catch (error) {
|
|
1242
|
+
console.log(chalk.yellow("\n⚠️ Erreur lors de l'installation, veuillez exécuter manuellement:"));
|
|
1243
|
+
console.log(chalk.gray(` cd ${moduleDir} && pnpm install`));
|
|
1244
|
+
}
|
|
1245
|
+
// Mettre à jour le registre des modules dans le core
|
|
1246
|
+
console.log(chalk.blue("\n📝 Mise à jour du registre des modules..."));
|
|
1247
|
+
await updateModuleRegistry(config, rootDir);
|
|
1248
|
+
// Build auto du module pour générer dist immédiatement
|
|
1249
|
+
console.log(chalk.blue("\n🏗️ Build initial du module..."));
|
|
1250
|
+
try {
|
|
1251
|
+
execSync(`pnpm --filter ${config.slug} build`, {
|
|
1252
|
+
cwd: rootDir,
|
|
1253
|
+
stdio: "inherit",
|
|
1254
|
+
});
|
|
1255
|
+
console.log(chalk.green("✓ Module compilé"));
|
|
1256
|
+
}
|
|
1257
|
+
catch (error) {
|
|
1258
|
+
console.log(chalk.yellow("⚠️ Build automatique échoué, exécutez: cd"), config.slug, "&& pnpm build");
|
|
1259
|
+
}
|
|
1041
1260
|
console.log(chalk.green(`\n✅ Module ${config.slug} créé avec succès!\n`));
|
|
1042
1261
|
console.log(chalk.gray(`📂 Emplacement: ${moduleDir}\n`));
|
|
1043
1262
|
console.log(chalk.blue("Prochaines étapes:"));
|
|
1044
|
-
console.log(chalk.gray(` 1.
|
|
1045
|
-
console.log(chalk.gray(
|
|
1046
|
-
console.log(chalk.gray(
|
|
1047
|
-
console.log(chalk.gray(
|
|
1263
|
+
console.log(chalk.gray(` 1. Ajouter à une app: pnpm lastbrain add-module ${config.slug.replace("module-", "")}`));
|
|
1264
|
+
console.log(chalk.gray(" 2. (Optionnel) Modifier Doc.tsx pour documentation personnalisée"));
|
|
1265
|
+
console.log(chalk.gray(" 3. Publier: pnpm publish:" + config.slug));
|
|
1266
|
+
console.log(chalk.gray(" 4. Générer docs globales: pnpm generate:all\n"));
|
|
1048
1267
|
}
|
|
1049
1268
|
/**
|
|
1050
1269
|
* Point d'entrée du script
|
|
@@ -1098,29 +1317,50 @@ export async function createModule() {
|
|
|
1098
1317
|
const pages = [];
|
|
1099
1318
|
// Pages publiques
|
|
1100
1319
|
const publicPages = parsePagesList(answers.pagesPublic);
|
|
1320
|
+
const invalidPublic = publicPages.filter((n) => RESERVED_PAGE_NAMES.has(n));
|
|
1321
|
+
if (invalidPublic.length) {
|
|
1322
|
+
console.error(chalk.red(`❌ Noms de pages publiques réservés détectés: ${invalidPublic.join(", ")}`));
|
|
1323
|
+
console.error(chalk.yellow("Noms interdits: layout, page, api, admin, auth, public. Choisissez des noms métier distincts."));
|
|
1324
|
+
process.exit(1);
|
|
1325
|
+
}
|
|
1101
1326
|
for (const pageName of publicPages) {
|
|
1327
|
+
const slugName = slugifyPageName(pageName);
|
|
1102
1328
|
pages.push({
|
|
1103
1329
|
section: "public",
|
|
1104
|
-
path: `/${
|
|
1105
|
-
name:
|
|
1330
|
+
path: `/${slugName}`,
|
|
1331
|
+
name: slugName,
|
|
1106
1332
|
});
|
|
1107
1333
|
}
|
|
1108
1334
|
// Pages auth
|
|
1109
1335
|
const authPages = parsePagesList(answers.pagesAuth);
|
|
1336
|
+
const invalidAuth = authPages.filter((n) => RESERVED_PAGE_NAMES.has(n));
|
|
1337
|
+
if (invalidAuth.length) {
|
|
1338
|
+
console.error(chalk.red(`❌ Noms de pages auth réservés détectés: ${invalidAuth.join(", ")}`));
|
|
1339
|
+
console.error(chalk.yellow("Noms interdits: layout, page, api, admin, auth, public. Utilisez des noms métier (ex: dashboard, profile)."));
|
|
1340
|
+
process.exit(1);
|
|
1341
|
+
}
|
|
1110
1342
|
for (const pageName of authPages) {
|
|
1343
|
+
const slugName = slugifyPageName(pageName);
|
|
1111
1344
|
pages.push({
|
|
1112
1345
|
section: "auth",
|
|
1113
|
-
path: `/${
|
|
1114
|
-
name:
|
|
1346
|
+
path: `/${slugName}`,
|
|
1347
|
+
name: slugName,
|
|
1115
1348
|
});
|
|
1116
1349
|
}
|
|
1117
1350
|
// Pages admin
|
|
1118
1351
|
const adminPages = parsePagesList(answers.pagesAdmin);
|
|
1352
|
+
const invalidAdmin = adminPages.filter((n) => RESERVED_PAGE_NAMES.has(n));
|
|
1353
|
+
if (invalidAdmin.length) {
|
|
1354
|
+
console.error(chalk.red(`❌ Noms de pages admin réservés détectés: ${invalidAdmin.join(", ")}`));
|
|
1355
|
+
console.error(chalk.yellow("Noms interdits: layout, page, api, admin, auth, public. Utilisez des noms métier (ex: settings, users)."));
|
|
1356
|
+
process.exit(1);
|
|
1357
|
+
}
|
|
1119
1358
|
for (const pageName of adminPages) {
|
|
1359
|
+
const slugName = slugifyPageName(pageName);
|
|
1120
1360
|
pages.push({
|
|
1121
1361
|
section: "admin",
|
|
1122
|
-
path: `/${
|
|
1123
|
-
name:
|
|
1362
|
+
path: `/${slugName}`,
|
|
1363
|
+
name: slugName,
|
|
1124
1364
|
});
|
|
1125
1365
|
}
|
|
1126
1366
|
// Tables
|
|
@@ -1150,19 +1390,12 @@ export async function createModule() {
|
|
|
1150
1390
|
tables,
|
|
1151
1391
|
};
|
|
1152
1392
|
// Trouver le répertoire racine du workspace (chercher pnpm-workspace.yaml)
|
|
1153
|
-
let rootDir
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
while (attempts < maxAttempts) {
|
|
1157
|
-
const workspaceFile = path.join(rootDir, "pnpm-workspace.yaml");
|
|
1158
|
-
if (fs.existsSync(workspaceFile)) {
|
|
1159
|
-
break;
|
|
1160
|
-
}
|
|
1161
|
-
rootDir = path.resolve(rootDir, "..");
|
|
1162
|
-
attempts++;
|
|
1393
|
+
let rootDir;
|
|
1394
|
+
try {
|
|
1395
|
+
rootDir = findWorkspaceRoot();
|
|
1163
1396
|
}
|
|
1164
|
-
|
|
1165
|
-
console.error(chalk.red("❌
|
|
1397
|
+
catch (error) {
|
|
1398
|
+
console.error(chalk.red("❌ " + error.message));
|
|
1166
1399
|
process.exit(1);
|
|
1167
1400
|
}
|
|
1168
1401
|
// Créer le module
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module-delete.d.ts","sourceRoot":"","sources":["../../src/scripts/module-delete.ts"],"names":[],"mappings":"AAaA;;;GAGG;AACH,wBAAsB,YAAY,kBAgLjC"}
|