@lastbrain/app 0.1.34 → 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/README.md +23 -5
- 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 +737 -52
- 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 +170 -30
- package/dist/templates/DocPage.d.ts.map +1 -1
- package/dist/templates/DocPage.js +25 -8
- package/dist/templates/migrations/20201010100000_app_base.sql +23 -24
- package/package.json +4 -4
- package/src/__tests__/module-registry.test.ts +5 -17
- package/src/scripts/db-init.ts +2 -2
- 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 +885 -63
- 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 +1163 -753
- package/src/templates/DocPage.tsx +28 -11
- package/src/templates/migrations/20201010100000_app_base.sql +23 -24
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
4
4
|
import { useState, useEffect } from "react";
|
|
5
5
|
import { Card, CardBody, CardHeader, Listbox, ListboxItem, Chip, Button, Drawer, DrawerContent, DrawerHeader, DrawerBody, Snippet, } from "@lastbrain/ui";
|
|
6
|
-
import { Menu, Home, Sparkles, Rocket, Building2, Package, Database, Palette, BookOpen, Link, Blocks, HardDrive, } from "lucide-react";
|
|
6
|
+
import { Menu, Home, Sparkles, Rocket, Building2, Package, Database, Palette, BookOpen, Link, Blocks, HardDrive, RotateCcw, } from "lucide-react";
|
|
7
7
|
import { DefaultDocumentation } from "./DefaultDoc.js";
|
|
8
|
-
const NavigationListbox = ({ navigationItems, selectedModule, scrollToSection, setSelectedModule, }) => (_jsx(Listbox, { "aria-label": "Navigation", selectionMode: "single", selectedKeys: selectedModule ? [selectedModule] : [], onSelectionChange: (keys) => {
|
|
8
|
+
const NavigationListbox = ({ navigationItems, selectedModule, scrollToSection, setSelectedModule, }) => (_jsx(Listbox, { "aria-label": "Navigation", selectionMode: "single", variant: "solid", selectedKeys: selectedModule ? [selectedModule] : [], onSelectionChange: (keys) => {
|
|
9
9
|
const key = Array.from(keys)[0];
|
|
10
10
|
if (key) {
|
|
11
11
|
scrollToSection(key);
|
|
@@ -16,8 +16,8 @@ const NavigationListbox = ({ navigationItems, selectedModule, scrollToSection, s
|
|
|
16
16
|
}
|
|
17
17
|
}, items: navigationItems, children: (item) => {
|
|
18
18
|
const IconComponent = item.icon;
|
|
19
|
-
return (_jsx(ListboxItem, { textValue: item.name, description: item.description, color:
|
|
20
|
-
} }));
|
|
19
|
+
return (_jsx(ListboxItem, { textValue: item.name, description: item.description, color: "default", variant: "solid", endContent: item.number && (_jsx(Chip, { size: "sm", color: "primary", children: item.number ?? 0 })), startContent: _jsx(IconComponent, { size: 18, className: "shrink-0" }), children: item.name }, item.id));
|
|
20
|
+
} }, `listbox-${selectedModule}`));
|
|
21
21
|
export function DocPage({ modules = [], defaultContent }) {
|
|
22
22
|
const [selectedModule, setSelectedModule] = useState("default");
|
|
23
23
|
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
|
@@ -92,62 +92,78 @@ export function DocPage({ modules = [], defaultContent }) {
|
|
|
92
92
|
name: "Documentation",
|
|
93
93
|
description: "Accueil",
|
|
94
94
|
icon: Home,
|
|
95
|
+
color: "default",
|
|
95
96
|
},
|
|
96
97
|
{
|
|
97
98
|
id: "section-welcome",
|
|
98
99
|
name: "Bienvenue",
|
|
99
100
|
description: "",
|
|
100
101
|
icon: Sparkles,
|
|
102
|
+
color: "default",
|
|
101
103
|
},
|
|
102
104
|
{
|
|
103
105
|
id: "section-quickstart",
|
|
104
106
|
name: "Démarrage rapide",
|
|
105
107
|
description: "",
|
|
106
108
|
icon: Rocket,
|
|
109
|
+
color: "default",
|
|
107
110
|
},
|
|
108
111
|
{
|
|
109
112
|
id: "section-architecture",
|
|
110
113
|
name: "Architecture",
|
|
111
114
|
description: "",
|
|
112
115
|
icon: Building2,
|
|
116
|
+
color: "default",
|
|
113
117
|
},
|
|
114
118
|
{
|
|
115
119
|
id: "section-create-module",
|
|
116
120
|
name: "Créer un module",
|
|
117
121
|
description: "",
|
|
118
122
|
icon: Package,
|
|
123
|
+
color: "default",
|
|
119
124
|
},
|
|
120
125
|
{
|
|
121
126
|
id: "section-database",
|
|
122
127
|
name: "Base de données",
|
|
123
128
|
description: "",
|
|
124
129
|
icon: Database,
|
|
130
|
+
color: "default",
|
|
125
131
|
},
|
|
126
132
|
{
|
|
127
133
|
id: "section-storage",
|
|
128
134
|
name: "Proxy Storage",
|
|
129
135
|
description: "Gestion des fichiers",
|
|
130
136
|
icon: HardDrive,
|
|
137
|
+
color: "default",
|
|
131
138
|
},
|
|
132
139
|
{
|
|
133
140
|
id: "section-realtime",
|
|
134
141
|
name: "Système Realtime",
|
|
135
142
|
description: "Synchronisation temps réel",
|
|
136
|
-
icon:
|
|
143
|
+
icon: RotateCcw,
|
|
144
|
+
color: "default",
|
|
137
145
|
},
|
|
138
146
|
{
|
|
139
147
|
id: "section-ui",
|
|
140
148
|
name: "Interface utilisateur",
|
|
141
149
|
description: "",
|
|
142
150
|
icon: Palette,
|
|
151
|
+
color: "default",
|
|
143
152
|
},
|
|
144
153
|
{
|
|
145
154
|
id: "section-module-docs",
|
|
146
155
|
name: "Doc des modules",
|
|
147
156
|
description: "",
|
|
148
157
|
icon: BookOpen,
|
|
158
|
+
color: "default",
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: "section-links",
|
|
162
|
+
name: "Liens utiles",
|
|
163
|
+
description: "",
|
|
164
|
+
icon: Link,
|
|
165
|
+
color: "default",
|
|
149
166
|
},
|
|
150
|
-
{ id: "section-links", name: "Liens utiles", description: "", icon: Link },
|
|
151
167
|
...(modules.length > 0
|
|
152
168
|
? [
|
|
153
169
|
{
|
|
@@ -156,6 +172,7 @@ export function DocPage({ modules = [], defaultContent }) {
|
|
|
156
172
|
description: "",
|
|
157
173
|
icon: Blocks,
|
|
158
174
|
number: modules.length,
|
|
175
|
+
color: "default",
|
|
159
176
|
},
|
|
160
177
|
]
|
|
161
178
|
: []),
|
|
@@ -170,9 +187,9 @@ export function DocPage({ modules = [], defaultContent }) {
|
|
|
170
187
|
color: "primary",
|
|
171
188
|
})),
|
|
172
189
|
];
|
|
173
|
-
return (_jsx("div", { className: "w-full pt-8 md:pt-12 pb-24 lg:pb-8", children: _jsxs("div", { className: "container mx-auto md:px-4 py-8", children: [_jsx("div", { className: "fixed w-full h-16 left-0 bottom-0 bg-background/20 backdrop-blur-lg z-50 lg:hidden p-2", children: _jsx("div", { className: "flex justify-center", children: _jsx(Button, { isIconOnly: true, variant: "solid", onPress: () => setIsDrawerOpen(true), children: _jsx(Menu, { size: 24 }) }) }) }), _jsx(Drawer, { isOpen: isDrawerOpen, onOpenChange: setIsDrawerOpen, placement: "left", children: _jsxs(DrawerContent, { children: [_jsx(DrawerHeader, { children: _jsx("h2", { className: "text-xl font-semibold", children: "Navigation" }) }), _jsx(DrawerBody, { children: _jsx(NavigationListbox, { navigationItems: navigationItems, selectedModule: selectedModule, scrollToSection: scrollToSection, setSelectedModule: setSelectedModule }) })] }) }), _jsxs("div", { className: "flex gap-
|
|
190
|
+
return (_jsx("div", { className: "w-full pt-8 md:pt-12 pb-24 lg:pb-8", children: _jsxs("div", { className: "container mx-auto md:px-4 py-8", children: [_jsx("div", { className: "fixed w-full h-16 left-0 bottom-0 bg-background/20 backdrop-blur-lg z-50 lg:hidden p-2", children: _jsx("div", { className: "flex justify-center", children: _jsx(Button, { isIconOnly: true, variant: "solid", onPress: () => setIsDrawerOpen(true), children: _jsx(Menu, { size: 24 }) }) }) }), _jsx(Drawer, { isOpen: isDrawerOpen, onOpenChange: setIsDrawerOpen, placement: "left", children: _jsxs(DrawerContent, { children: [_jsx(DrawerHeader, { children: _jsx("h2", { className: "text-xl font-semibold", children: "Navigation" }) }), _jsx(DrawerBody, { children: _jsx(NavigationListbox, { navigationItems: navigationItems, selectedModule: selectedModule, scrollToSection: scrollToSection, setSelectedModule: setSelectedModule }) })] }) }), _jsxs("div", { className: "flex gap-4", children: [_jsx("aside", { className: "hidden lg:block w-72 shrink-0 sticky top-18 self-start", children: _jsx(Card, { children: _jsx(CardBody, { children: _jsx(NavigationListbox, { navigationItems: navigationItems, selectedModule: selectedModule, scrollToSection: scrollToSection, setSelectedModule: setSelectedModule }) }) }) }), _jsxs("main", { className: "flex-1 w-full min-w-0 space-y-8", children: [defaultContent ? (_jsx("div", { children: defaultContent })) : (_jsx(DefaultDocumentation, {})), modules.length > 0 && (_jsxs("div", { className: "space-y-6", children: [_jsxs(Card, { id: "section-modules", className: "scroll-mt-32", children: [_jsx(CardHeader, { children: _jsx("h2", { className: "text-2xl font-semibold", children: "Modules disponibles" }) }), _jsxs(CardBody, { children: [_jsx("p", { className: "text-slate-600 dark:text-slate-400 mb-4", children: "Voici la liste de tous les modules disponibles dans LastBrain. Les modules actifs sont utilis\u00E9s dans votre application." }), _jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: modules.map((module) => (_jsx(Card, { isPressable: module.available, onPress: () => module.available && scrollToSection(module.id), className: `${module.available
|
|
174
191
|
? "cursor-pointer hover:shadow-lg"
|
|
175
|
-
: "opacity-70"} transition-shadow`, children: _jsxs(CardBody, { children: [_jsxs("div", { className: "flex items-start justify-between mb-2", children: [
|
|
192
|
+
: "opacity-70"} transition-shadow`, children: _jsxs(CardBody, { children: [_jsxs("div", { className: "flex items-start justify-between mb-2", children: [_jsxs("h3", { className: "text-lg font-semibold flex flex-inline items-center gap-2", children: [_jsx(Blocks, { size: 20, className: "shrink-0" }), module.name] }), _jsx(Chip, { size: "sm", color: module.available ? "success" : "warning", variant: "flat", children: module.available ? "Actif" : "Inactif" })] }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: module.description }), !module.available && (_jsxs("div", { className: "flex justify-between items-center text-xs text-default-700 mt-2", children: [_jsx("span", { children: "Pour activer : " }), _jsx(Snippet, { hideSymbol: true, color: "primary", children: `pnpm lastbrain add-module ${module.id}` })] }))] }) }, module.id))) })] })] }), modules
|
|
176
193
|
.filter((m) => m.available)
|
|
177
194
|
.map((module) => (_jsx("div", { id: `module-${module.id}`, className: "scroll-mt-32", children: module.content }, module.id)))] }))] })] })] }) }));
|
|
178
195
|
}
|
|
@@ -177,33 +177,32 @@ BEGIN
|
|
|
177
177
|
), false
|
|
178
178
|
) as is_foreign_key,
|
|
179
179
|
(
|
|
180
|
-
SELECT
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
AND
|
|
180
|
+
SELECT
|
|
181
|
+
CASE
|
|
182
|
+
WHEN nsp.nspname = 'public' THEN ref_table.relname::text
|
|
183
|
+
ELSE nsp.nspname::text || '.' || ref_table.relname::text
|
|
184
|
+
END
|
|
185
|
+
FROM pg_constraint con
|
|
186
|
+
JOIN pg_attribute att ON att.attrelid = con.conrelid AND att.attnum = ANY(con.conkey)
|
|
187
|
+
JOIN pg_class tbl ON tbl.oid = con.conrelid
|
|
188
|
+
JOIN pg_class ref_table ON ref_table.oid = con.confrelid
|
|
189
|
+
JOIN pg_namespace nsp ON nsp.oid = ref_table.relnamespace
|
|
190
|
+
WHERE con.contype = 'f'
|
|
191
|
+
AND tbl.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')
|
|
192
|
+
AND tbl.relname = p_table_name
|
|
193
|
+
AND att.attname = c.column_name
|
|
192
194
|
LIMIT 1
|
|
193
195
|
) as foreign_table,
|
|
194
196
|
(
|
|
195
|
-
SELECT
|
|
196
|
-
FROM
|
|
197
|
-
JOIN
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
AND
|
|
203
|
-
|
|
204
|
-
AND tc.table_schema = 'public'
|
|
205
|
-
AND tc.table_name = p_table_name
|
|
206
|
-
AND kcu.column_name = c.column_name
|
|
197
|
+
SELECT ref_att.attname::text
|
|
198
|
+
FROM pg_constraint con
|
|
199
|
+
JOIN pg_attribute att ON att.attrelid = con.conrelid AND att.attnum = ANY(con.conkey)
|
|
200
|
+
JOIN pg_class tbl ON tbl.oid = con.conrelid
|
|
201
|
+
JOIN pg_attribute ref_att ON ref_att.attrelid = con.confrelid AND ref_att.attnum = ANY(con.confkey)
|
|
202
|
+
WHERE con.contype = 'f'
|
|
203
|
+
AND tbl.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')
|
|
204
|
+
AND tbl.relname = p_table_name
|
|
205
|
+
AND att.attname = c.column_name
|
|
207
206
|
LIMIT 1
|
|
208
207
|
) as foreign_column,
|
|
209
208
|
pgd.description::text
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lastbrain/app",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.37",
|
|
4
4
|
"description": "Framework modulaire Next.js avec CLI et système de modules",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -33,11 +33,11 @@
|
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@lastbrain/core": "^0.1.0",
|
|
35
35
|
"@lastbrain/ui": "^0.1.4",
|
|
36
|
-
"@supabase/supabase-js": "^2.
|
|
36
|
+
"@supabase/supabase-js": "^2.84.0",
|
|
37
37
|
"chalk": "^5.3.0",
|
|
38
|
-
"commander": "^
|
|
38
|
+
"commander": "^14.0.2",
|
|
39
39
|
"fs-extra": "^11.2.0",
|
|
40
|
-
"inquirer": "^
|
|
40
|
+
"inquirer": "^13.0.1",
|
|
41
41
|
"lucide-react": "^0.554.0",
|
|
42
42
|
"next-themes": "^0.4.6",
|
|
43
43
|
"react": "^19.0.0",
|
|
@@ -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/db-init.ts
CHANGED
|
@@ -117,7 +117,7 @@ function parseEnvFile(filePath: string) {
|
|
|
117
117
|
function ensureEnvFile(values: Record<string, string>) {
|
|
118
118
|
envTargets.forEach((target) => {
|
|
119
119
|
let existingVars: Record<string, string> = {};
|
|
120
|
-
|
|
120
|
+
|
|
121
121
|
// Lire le fichier .env.local existant pour préserver les variables personnalisées
|
|
122
122
|
if (fs.existsSync(target)) {
|
|
123
123
|
try {
|
|
@@ -138,7 +138,7 @@ function ensureEnvFile(values: Record<string, string>) {
|
|
|
138
138
|
|
|
139
139
|
// Fusionner les nouvelles valeurs avec les existantes (nouvelles valeurs prioritaires)
|
|
140
140
|
const mergedValues = { ...existingVars, ...values };
|
|
141
|
-
|
|
141
|
+
|
|
142
142
|
const content = Object.entries(mergedValues)
|
|
143
143
|
.map(([key, value]) => `${key}=${value}`)
|
|
144
144
|
.join("\n");
|
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
|
}
|