@lastbrain/app 0.1.36 → 0.1.37
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/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/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 +1121 -424
- package/src/templates/DocPage.tsx +26 -10
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { AVAILABLE_MODULES } from "
|
|
2
|
+
import { AVAILABLE_MODULES } from "@lastbrain/core/config/modules";
|
|
3
3
|
|
|
4
4
|
describe("Module Registry", () => {
|
|
5
5
|
describe("AVAILABLE_MODULES", () => {
|
|
@@ -11,32 +11,20 @@ describe("Module Registry", () => {
|
|
|
11
11
|
const authModule = AVAILABLE_MODULES.find((m) => m.name === "auth");
|
|
12
12
|
expect(authModule).toBeDefined();
|
|
13
13
|
expect(authModule?.package).toBe("@lastbrain/module-auth");
|
|
14
|
-
expect(authModule?.hasMigrations).toBe(true);
|
|
15
14
|
});
|
|
16
15
|
|
|
17
16
|
it("should have ai module defined", () => {
|
|
18
17
|
const aiModule = AVAILABLE_MODULES.find((m) => m.name === "ai");
|
|
19
18
|
expect(aiModule).toBeDefined();
|
|
20
19
|
expect(aiModule?.package).toBe("@lastbrain/module-ai");
|
|
21
|
-
expect(aiModule?.hasMigrations).toBe(true);
|
|
22
20
|
});
|
|
23
21
|
|
|
24
22
|
it("should have all required properties for each module", () => {
|
|
25
23
|
AVAILABLE_MODULES.forEach((module) => {
|
|
26
24
|
expect(module.name).toBeDefined();
|
|
27
25
|
expect(module.package).toBeDefined();
|
|
28
|
-
expect(module.
|
|
26
|
+
expect(module.emoji).toBeDefined();
|
|
29
27
|
expect(module.description).toBeDefined();
|
|
30
|
-
expect(typeof module.hasMigrations).toBe("boolean");
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it("should have migrations paths when hasMigrations is true", () => {
|
|
35
|
-
AVAILABLE_MODULES.forEach((module) => {
|
|
36
|
-
if (module.hasMigrations) {
|
|
37
|
-
expect(module.migrationsPath).toBeDefined();
|
|
38
|
-
expect(module.migrationsDownPath).toBeDefined();
|
|
39
|
-
}
|
|
40
28
|
});
|
|
41
29
|
});
|
|
42
30
|
|
|
@@ -52,10 +40,10 @@ describe("Module Registry", () => {
|
|
|
52
40
|
expect(packages.length).toBe(uniquePackages.size);
|
|
53
41
|
});
|
|
54
42
|
|
|
55
|
-
it("should have
|
|
43
|
+
it("should have emoji", () => {
|
|
56
44
|
AVAILABLE_MODULES.forEach((module) => {
|
|
57
|
-
// Check if
|
|
58
|
-
expect(module.
|
|
45
|
+
// Check if emoji contains at least one emoji (basic check)
|
|
46
|
+
expect(module.emoji).toMatch(/[\u{1F300}-\u{1F9FF}]/u);
|
|
59
47
|
});
|
|
60
48
|
});
|
|
61
49
|
|
package/src/scripts/init-app.ts
CHANGED
|
@@ -4,7 +4,10 @@ import { fileURLToPath } from "url";
|
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import inquirer from "inquirer";
|
|
6
6
|
import { execSync } from "child_process";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
AVAILABLE_MODULES,
|
|
9
|
+
type ModuleMetadata,
|
|
10
|
+
} from "@lastbrain/core/config/modules";
|
|
8
11
|
|
|
9
12
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
13
|
const __dirname = path.dirname(__filename);
|
|
@@ -47,7 +50,7 @@ export async function initApp(options: InitAppOptions) {
|
|
|
47
50
|
name: "modules",
|
|
48
51
|
message: "Quels modules voulez-vous installer ?",
|
|
49
52
|
choices: AVAILABLE_MODULES.map((module) => ({
|
|
50
|
-
name: `${module.
|
|
53
|
+
name: `${module.emoji} ${module.name.charAt(0).toUpperCase() + module.name.slice(1)} - ${module.description}`,
|
|
51
54
|
value: module.name,
|
|
52
55
|
checked: false,
|
|
53
56
|
})),
|
|
@@ -3,6 +3,10 @@ import path from "path";
|
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { execSync } from "child_process";
|
|
5
5
|
import inquirer from "inquirer";
|
|
6
|
+
import {
|
|
7
|
+
AVAILABLE_MODULES,
|
|
8
|
+
type ModuleMetadata,
|
|
9
|
+
} from "@lastbrain/core/config/modules";
|
|
6
10
|
|
|
7
11
|
interface ModuleDefinition {
|
|
8
12
|
name: string;
|
|
@@ -14,44 +18,35 @@ interface ModuleDefinition {
|
|
|
14
18
|
migrationsDownPath?: string;
|
|
15
19
|
}
|
|
16
20
|
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
{
|
|
20
|
-
name:
|
|
21
|
-
package:
|
|
22
|
-
displayName:
|
|
23
|
-
description:
|
|
24
|
-
"Système d'authentification complet (signin, signup, sessions)",
|
|
21
|
+
// Convert core module metadata to local module definition
|
|
22
|
+
function toModuleDefinition(meta: ModuleMetadata): ModuleDefinition {
|
|
23
|
+
return {
|
|
24
|
+
name: meta.name,
|
|
25
|
+
package: meta.package,
|
|
26
|
+
displayName: `${meta.emoji} ${meta.name.charAt(0).toUpperCase() + meta.name.slice(1)}`,
|
|
27
|
+
description: meta.description,
|
|
25
28
|
hasMigrations: true,
|
|
26
29
|
migrationsPath: "supabase/migrations",
|
|
27
30
|
migrationsDownPath: "supabase/migrations-down",
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
name: "ai",
|
|
31
|
-
package: "@lastbrain/module-ai",
|
|
32
|
-
displayName: "🤖 AI Generation",
|
|
33
|
-
description: "Génération de texte et d'images avec gestion de tokens",
|
|
34
|
-
hasMigrations: true,
|
|
35
|
-
migrationsPath: "supabase/migrations",
|
|
36
|
-
migrationsDownPath: "supabase/migrations-down",
|
|
37
|
-
},
|
|
38
|
-
// Ajouter d'autres modules ici au fur et à mesure
|
|
39
|
-
];
|
|
31
|
+
};
|
|
32
|
+
}
|
|
40
33
|
|
|
41
34
|
export async function addModule(moduleName: string, targetDir: string) {
|
|
42
35
|
console.log(chalk.blue(`\n🔧 Ajout du module: ${moduleName}\n`));
|
|
43
36
|
|
|
44
|
-
const
|
|
45
|
-
if (!
|
|
37
|
+
const moduleMeta = AVAILABLE_MODULES.find((m) => m.name === moduleName);
|
|
38
|
+
if (!moduleMeta) {
|
|
46
39
|
console.error(
|
|
47
40
|
chalk.red(`❌ Module "${moduleName}" non trouvé. Modules disponibles:`),
|
|
48
41
|
);
|
|
49
42
|
AVAILABLE_MODULES.forEach((m) => {
|
|
50
|
-
console.log(chalk.gray(` - ${m.name}: ${m.description}`));
|
|
43
|
+
console.log(chalk.gray(` - ${m.emoji} ${m.name}: ${m.description}`));
|
|
51
44
|
});
|
|
52
45
|
process.exit(1);
|
|
53
46
|
}
|
|
54
47
|
|
|
48
|
+
const module = toModuleDefinition(moduleMeta);
|
|
49
|
+
|
|
55
50
|
// 1. Vérifier qu'on est dans un projet LastBrain
|
|
56
51
|
const pkgPath = path.join(targetDir, "package.json");
|
|
57
52
|
if (!fs.existsSync(pkgPath)) {
|
|
@@ -85,6 +80,43 @@ export async function addModule(moduleName: string, targetDir: string) {
|
|
|
85
80
|
process.exit(1);
|
|
86
81
|
}
|
|
87
82
|
|
|
83
|
+
// 3.1 Build du module ajouté (pour générer dist/ et éviter les erreurs d'import)
|
|
84
|
+
console.log(chalk.yellow(`\n🏗️ Build du module ${module.package}...`));
|
|
85
|
+
try {
|
|
86
|
+
// Trouver la racine du monorepo (chercher pnpm-workspace.yaml en remontant)
|
|
87
|
+
let workspaceRoot = targetDir;
|
|
88
|
+
let attempts = 0;
|
|
89
|
+
const maxAttempts = 6;
|
|
90
|
+
while (attempts < maxAttempts) {
|
|
91
|
+
if (fs.existsSync(path.join(workspaceRoot, "pnpm-workspace.yaml"))) {
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
const parent = path.resolve(workspaceRoot, "..");
|
|
95
|
+
if (parent === workspaceRoot) break;
|
|
96
|
+
workspaceRoot = parent;
|
|
97
|
+
attempts++;
|
|
98
|
+
}
|
|
99
|
+
if (!fs.existsSync(path.join(workspaceRoot, "pnpm-workspace.yaml"))) {
|
|
100
|
+
console.log(
|
|
101
|
+
chalk.gray(
|
|
102
|
+
" ℹ️ Impossible de localiser la racine du monorepo, build ignoré",
|
|
103
|
+
),
|
|
104
|
+
);
|
|
105
|
+
} else {
|
|
106
|
+
execSync(`pnpm --filter ${module.package} build`, {
|
|
107
|
+
cwd: workspaceRoot,
|
|
108
|
+
stdio: "inherit",
|
|
109
|
+
});
|
|
110
|
+
console.log(chalk.green(" ✓ Module compilé"));
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
console.log(
|
|
114
|
+
chalk.yellow(" ⚠️ Build du module échoué, essayez: pnpm --filter"),
|
|
115
|
+
module.package,
|
|
116
|
+
"build",
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
88
120
|
// 5. Copier les migrations du module
|
|
89
121
|
const copiedMigrationFiles: string[] = [];
|
|
90
122
|
if (module.hasMigrations) {
|
|
@@ -13,6 +13,10 @@ import type {
|
|
|
13
13
|
|
|
14
14
|
// Utiliser PROJECT_ROOT si défini (pour pnpm --filter), sinon process.cwd()
|
|
15
15
|
const projectRoot = process.env.PROJECT_ROOT || process.cwd();
|
|
16
|
+
// Si on est dans une app, monter jusqu'à la racine du monorepo
|
|
17
|
+
const monorepoRoot = projectRoot.includes("/apps/")
|
|
18
|
+
? path.resolve(projectRoot, "..", "..")
|
|
19
|
+
: projectRoot;
|
|
16
20
|
const appDirectory = path.join(projectRoot, "app");
|
|
17
21
|
|
|
18
22
|
// Créer un require dans le contexte de l'application pour résoudre les modules installés dans l'app
|
|
@@ -207,9 +211,40 @@ function buildPage(moduleConfig: ModuleBuildConfig, page: ModulePageConfig) {
|
|
|
207
211
|
page.path.includes("signup") ||
|
|
208
212
|
page.path.includes("reset-password"));
|
|
209
213
|
|
|
214
|
+
// Détecter si c'est la page de détail utilisateur qui a besoin des user tabs
|
|
215
|
+
const isUserDetailPage =
|
|
216
|
+
page.section === "admin" &&
|
|
217
|
+
page.path.includes("users/[id]") &&
|
|
218
|
+
page.componentExport === "UserPage";
|
|
219
|
+
|
|
210
220
|
let content: string;
|
|
211
221
|
|
|
212
|
-
if (
|
|
222
|
+
if (isUserDetailPage) {
|
|
223
|
+
// Page spéciale SSR avec injection des user tabs
|
|
224
|
+
// On importe directement depuis app/config au lieu de passer via props
|
|
225
|
+
content = `// GENERATED BY LASTBRAIN MODULE BUILD
|
|
226
|
+
import { UserDetailPage } from "${moduleConfig.moduleName}";
|
|
227
|
+
|
|
228
|
+
interface UserPageProps { params: Promise<{ id: string }> }
|
|
229
|
+
|
|
230
|
+
async function getModuleUserTabs() {
|
|
231
|
+
try {
|
|
232
|
+
// Depuis /app/admin/auth/users/[id]/ vers /apps/test-01/config/user-tabs
|
|
233
|
+
const { moduleUserTabs } = await import("../../../../../config/user-tabs");
|
|
234
|
+
return moduleUserTabs || [];
|
|
235
|
+
} catch (e) {
|
|
236
|
+
console.warn("[user-detail-wrapper] erreur chargement user-tabs", e);
|
|
237
|
+
return [];
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export default async function ${wrapperName}(props: UserPageProps) {
|
|
242
|
+
const { id } = await props.params;
|
|
243
|
+
const moduleUserTabs = await getModuleUserTabs();
|
|
244
|
+
return <UserDetailPage userId={id} moduleUserTabs={moduleUserTabs} />;
|
|
245
|
+
}
|
|
246
|
+
`;
|
|
247
|
+
} else if (isPublicAuthPage) {
|
|
213
248
|
content = `// GENERATED BY LASTBRAIN MODULE BUILD
|
|
214
249
|
"use client";
|
|
215
250
|
|
|
@@ -314,6 +349,8 @@ export interface MenuItem {
|
|
|
314
349
|
icon?: string;
|
|
315
350
|
path: string;
|
|
316
351
|
order?: number;
|
|
352
|
+
shortcut?: string;
|
|
353
|
+
shortcutDisplay?: string;
|
|
317
354
|
}
|
|
318
355
|
|
|
319
356
|
export interface MenuConfig {
|
|
@@ -838,6 +875,73 @@ export default realtimeConfig;
|
|
|
838
875
|
}
|
|
839
876
|
}
|
|
840
877
|
|
|
878
|
+
async function generateUserTabsConfig(moduleConfigs: ModuleBuildConfig[]) {
|
|
879
|
+
try {
|
|
880
|
+
// Extraire les configurations user tabs des modules
|
|
881
|
+
const userTabsConfigs = moduleConfigs
|
|
882
|
+
.filter((config) => config.userTabs && config.userTabs.length > 0)
|
|
883
|
+
.flatMap((config) =>
|
|
884
|
+
config.userTabs!.map((tab) => ({
|
|
885
|
+
...tab,
|
|
886
|
+
moduleName: config.moduleName,
|
|
887
|
+
})),
|
|
888
|
+
)
|
|
889
|
+
.sort((a, b) => (a.order || 0) - (b.order || 0));
|
|
890
|
+
|
|
891
|
+
if (userTabsConfigs.length === 0) {
|
|
892
|
+
console.log("⏭️ No user tabs configuration found in modules");
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// Générer les imports statiques (Next/dynamic pour chaque composant)
|
|
897
|
+
const importsForApp = userTabsConfigs
|
|
898
|
+
.map(
|
|
899
|
+
(tab) =>
|
|
900
|
+
`const ${tab.componentExport} = dynamic(() => import("${tab.moduleName}").then(mod => ({ default: mod.${tab.componentExport} })), { ssr: true });`,
|
|
901
|
+
)
|
|
902
|
+
.join("\n");
|
|
903
|
+
|
|
904
|
+
// Générer le tableau des tabs
|
|
905
|
+
const tabsArray = userTabsConfigs
|
|
906
|
+
.map(
|
|
907
|
+
(tab) => ` {
|
|
908
|
+
key: "${tab.key}",
|
|
909
|
+
title: "${tab.title}",
|
|
910
|
+
icon: "${tab.icon || ""}",
|
|
911
|
+
component: ${tab.componentExport},
|
|
912
|
+
}`,
|
|
913
|
+
)
|
|
914
|
+
.join(",\n");
|
|
915
|
+
|
|
916
|
+
const timestamp = new Date().toISOString();
|
|
917
|
+
const appContent = `// GENERATED FILE - DO NOT EDIT MANUALLY\n// User tabs configuration\n// Generated at: ${timestamp}\n\n"use client";\n\nimport dynamic from "next/dynamic";\nimport type React from "react";\n\n${importsForApp}\n\nexport interface ModuleUserTab {\n key: string;\n title: string;\n icon?: string;\n component: React.ComponentType<{ userId: string }>;\n}\n\nexport const moduleUserTabs: ModuleUserTab[] = [\n${tabsArray}\n];\n\nexport default moduleUserTabs;\n`;
|
|
918
|
+
|
|
919
|
+
// Créer le fichier de configuration (uniquement dans /config)
|
|
920
|
+
const outputPath = path.join(projectRoot, "config", "user-tabs.ts");
|
|
921
|
+
const configDir = path.dirname(outputPath);
|
|
922
|
+
|
|
923
|
+
// Créer le dossier config s'il n'existe pas
|
|
924
|
+
if (!fs.existsSync(configDir)) {
|
|
925
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// Écrire le fichier TypeScript
|
|
929
|
+
fs.writeFileSync(outputPath, appContent);
|
|
930
|
+
|
|
931
|
+
console.log(`✅ Generated user tabs configuration: ${outputPath}`);
|
|
932
|
+
console.log(`📊 User tabs count: ${userTabsConfigs.length}`);
|
|
933
|
+
|
|
934
|
+
// Afficher un résumé
|
|
935
|
+
userTabsConfigs.forEach((tab) => {
|
|
936
|
+
console.log(` - ${tab.title} (${tab.moduleName})`);
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
// Plus de copie vers app/config ni stub core
|
|
940
|
+
} catch (error) {
|
|
941
|
+
console.error("❌ Error generating user tabs configuration:", error);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
841
945
|
export async function runModuleBuild() {
|
|
842
946
|
ensureDirectory(appDirectory);
|
|
843
947
|
|
|
@@ -886,4 +990,8 @@ export async function runModuleBuild() {
|
|
|
886
990
|
// Générer la configuration realtime
|
|
887
991
|
console.log("🔄 Generating realtime configuration...");
|
|
888
992
|
await generateRealtimeConfig(moduleConfigs);
|
|
993
|
+
|
|
994
|
+
// Générer la configuration des user tabs
|
|
995
|
+
console.log("📑 Generating user tabs configuration...");
|
|
996
|
+
await generateUserTabsConfig(moduleConfigs);
|
|
889
997
|
}
|