@lastbrain/app 0.1.0

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.
Files changed (159) hide show
  1. package/README.md +256 -0
  2. package/dist/app-shell/(admin)/dashboard/page.d.ts +2 -0
  3. package/dist/app-shell/(admin)/dashboard/page.d.ts.map +1 -0
  4. package/dist/app-shell/(admin)/dashboard/page.js +5 -0
  5. package/dist/app-shell/(admin)/layout.d.ts +3 -0
  6. package/dist/app-shell/(admin)/layout.d.ts.map +1 -0
  7. package/dist/app-shell/(admin)/layout.js +5 -0
  8. package/dist/app-shell/(admin)/page.d.ts +2 -0
  9. package/dist/app-shell/(admin)/page.d.ts.map +1 -0
  10. package/dist/app-shell/(admin)/page.js +5 -0
  11. package/dist/app-shell/(auth)/dashboard/page.d.ts +2 -0
  12. package/dist/app-shell/(auth)/dashboard/page.d.ts.map +1 -0
  13. package/dist/app-shell/(auth)/dashboard/page.js +5 -0
  14. package/dist/app-shell/(auth)/layout.d.ts +3 -0
  15. package/dist/app-shell/(auth)/layout.d.ts.map +1 -0
  16. package/dist/app-shell/(auth)/layout.js +5 -0
  17. package/dist/app-shell/(auth)/page.d.ts +2 -0
  18. package/dist/app-shell/(auth)/page.d.ts.map +1 -0
  19. package/dist/app-shell/(auth)/page.js +5 -0
  20. package/dist/app-shell/(public)/page.d.ts +2 -0
  21. package/dist/app-shell/(public)/page.d.ts.map +1 -0
  22. package/dist/app-shell/(public)/page.js +5 -0
  23. package/dist/app-shell/layout.d.ts +3 -0
  24. package/dist/app-shell/layout.d.ts.map +1 -0
  25. package/dist/app-shell/layout.js +3 -0
  26. package/dist/app-shell/not-found.d.ts +2 -0
  27. package/dist/app-shell/not-found.d.ts.map +1 -0
  28. package/dist/app-shell/not-found.js +10 -0
  29. package/dist/auth/authHelpers.d.ts +7 -0
  30. package/dist/auth/authHelpers.d.ts.map +1 -0
  31. package/dist/auth/authHelpers.js +19 -0
  32. package/dist/auth/useAuthSession.d.ts +7 -0
  33. package/dist/auth/useAuthSession.d.ts.map +1 -0
  34. package/dist/auth/useAuthSession.js +43 -0
  35. package/dist/cli.d.ts +3 -0
  36. package/dist/cli.d.ts.map +1 -0
  37. package/dist/cli.js +88 -0
  38. package/dist/components/TableStructure.d.ts +8 -0
  39. package/dist/components/TableStructure.d.ts.map +1 -0
  40. package/dist/components/TableStructure.js +37 -0
  41. package/dist/index.d.ts +15 -0
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +15 -0
  44. package/dist/layouts/AdminLayout.d.ts +3 -0
  45. package/dist/layouts/AdminLayout.d.ts.map +1 -0
  46. package/dist/layouts/AdminLayout.js +5 -0
  47. package/dist/layouts/AppProviders.d.ts +13 -0
  48. package/dist/layouts/AppProviders.d.ts.map +1 -0
  49. package/dist/layouts/AppProviders.js +35 -0
  50. package/dist/layouts/AuthLayout.d.ts +3 -0
  51. package/dist/layouts/AuthLayout.d.ts.map +1 -0
  52. package/dist/layouts/AuthLayout.js +5 -0
  53. package/dist/layouts/PublicLayout.d.ts +3 -0
  54. package/dist/layouts/PublicLayout.d.ts.map +1 -0
  55. package/dist/layouts/PublicLayout.js +5 -0
  56. package/dist/layouts/RootLayout.d.ts +3 -0
  57. package/dist/layouts/RootLayout.d.ts.map +1 -0
  58. package/dist/layouts/RootLayout.js +8 -0
  59. package/dist/module-build.d.ts +2 -0
  60. package/dist/module-build.d.ts.map +1 -0
  61. package/dist/module-build.js +50 -0
  62. package/dist/modules/index.d.ts +3 -0
  63. package/dist/modules/index.d.ts.map +1 -0
  64. package/dist/modules/index.js +2 -0
  65. package/dist/modules/module-loader.d.ts +5 -0
  66. package/dist/modules/module-loader.d.ts.map +1 -0
  67. package/dist/modules/module-loader.js +10 -0
  68. package/dist/scripts/db-init.d.ts +2 -0
  69. package/dist/scripts/db-init.d.ts.map +1 -0
  70. package/dist/scripts/db-init.js +281 -0
  71. package/dist/scripts/db-migrations-sync.d.ts +2 -0
  72. package/dist/scripts/db-migrations-sync.d.ts.map +1 -0
  73. package/dist/scripts/db-migrations-sync.js +55 -0
  74. package/dist/scripts/dev-sync.d.ts +2 -0
  75. package/dist/scripts/dev-sync.d.ts.map +1 -0
  76. package/dist/scripts/dev-sync.js +182 -0
  77. package/dist/scripts/init-app.d.ts +11 -0
  78. package/dist/scripts/init-app.d.ts.map +1 -0
  79. package/dist/scripts/init-app.js +846 -0
  80. package/dist/scripts/module-add.d.ts +13 -0
  81. package/dist/scripts/module-add.d.ts.map +1 -0
  82. package/dist/scripts/module-add.js +178 -0
  83. package/dist/scripts/module-build.d.ts +2 -0
  84. package/dist/scripts/module-build.d.ts.map +1 -0
  85. package/dist/scripts/module-build.js +413 -0
  86. package/dist/scripts/module-create.d.ts +5 -0
  87. package/dist/scripts/module-create.d.ts.map +1 -0
  88. package/dist/scripts/module-create.js +694 -0
  89. package/dist/scripts/module-list.d.ts +2 -0
  90. package/dist/scripts/module-list.d.ts.map +1 -0
  91. package/dist/scripts/module-list.js +31 -0
  92. package/dist/scripts/module-remove.d.ts +2 -0
  93. package/dist/scripts/module-remove.d.ts.map +1 -0
  94. package/dist/scripts/module-remove.js +282 -0
  95. package/dist/scripts/readme-build.d.ts +2 -0
  96. package/dist/scripts/readme-build.d.ts.map +1 -0
  97. package/dist/scripts/readme-build.js +39 -0
  98. package/dist/templates/AuthGuidePage.d.ts +2 -0
  99. package/dist/templates/AuthGuidePage.d.ts.map +1 -0
  100. package/dist/templates/AuthGuidePage.js +9 -0
  101. package/dist/templates/DefaultDoc.d.ts +2 -0
  102. package/dist/templates/DefaultDoc.d.ts.map +1 -0
  103. package/dist/templates/DefaultDoc.js +29 -0
  104. package/dist/templates/DocPage.d.ts +17 -0
  105. package/dist/templates/DocPage.d.ts.map +1 -0
  106. package/dist/templates/DocPage.js +165 -0
  107. package/dist/templates/DocsPageWithModules.d.ts +2 -0
  108. package/dist/templates/DocsPageWithModules.d.ts.map +1 -0
  109. package/dist/templates/DocsPageWithModules.js +8 -0
  110. package/dist/templates/HomePage.d.ts +6 -0
  111. package/dist/templates/HomePage.d.ts.map +1 -0
  112. package/dist/templates/HomePage.js +6 -0
  113. package/dist/templates/MigrationsGuidePage.d.ts +2 -0
  114. package/dist/templates/MigrationsGuidePage.d.ts.map +1 -0
  115. package/dist/templates/MigrationsGuidePage.js +11 -0
  116. package/dist/templates/ModuleGuidePage.d.ts +2 -0
  117. package/dist/templates/ModuleGuidePage.d.ts.map +1 -0
  118. package/dist/templates/ModuleGuidePage.js +14 -0
  119. package/dist/templates/SimpleDocPage.d.ts +2 -0
  120. package/dist/templates/SimpleDocPage.d.ts.map +1 -0
  121. package/dist/templates/SimpleDocPage.js +28 -0
  122. package/dist/templates/SimpleHomePage.d.ts +6 -0
  123. package/dist/templates/SimpleHomePage.d.ts.map +1 -0
  124. package/dist/templates/SimpleHomePage.js +7 -0
  125. package/dist/templates/env.example/.env.example +6 -0
  126. package/dist/templates/migrations/20201010100000_app_base.sql +228 -0
  127. package/dist/templates/migrations/20201010100000_init.sql +123 -0
  128. package/package.json +75 -0
  129. package/src/app-shell/(admin)/layout.tsx +13 -0
  130. package/src/app-shell/(auth)/layout.tsx +13 -0
  131. package/src/app-shell/(public)/page.tsx +11 -0
  132. package/src/app-shell/layout.tsx +5 -0
  133. package/src/app-shell/not-found.tsx +28 -0
  134. package/src/layouts/AdminLayout.tsx +7 -0
  135. package/src/layouts/AppProviders.tsx +61 -0
  136. package/src/layouts/AuthLayout.tsx +7 -0
  137. package/src/layouts/PublicLayout.tsx +7 -0
  138. package/src/layouts/RootLayout.tsx +27 -0
  139. package/src/scripts/README.md +262 -0
  140. package/src/scripts/db-init.ts +338 -0
  141. package/src/scripts/db-migrations-sync.ts +86 -0
  142. package/src/scripts/dev-sync.ts +218 -0
  143. package/src/scripts/init-app.ts +1011 -0
  144. package/src/scripts/module-add.ts +242 -0
  145. package/src/scripts/module-build.ts +502 -0
  146. package/src/scripts/module-create.ts +809 -0
  147. package/src/scripts/module-list.ts +37 -0
  148. package/src/scripts/module-remove.ts +367 -0
  149. package/src/scripts/readme-build.ts +60 -0
  150. package/src/templates/AuthGuidePage.tsx +68 -0
  151. package/src/templates/DefaultDoc.tsx +462 -0
  152. package/src/templates/DocPage.tsx +381 -0
  153. package/src/templates/DocsPageWithModules.tsx +22 -0
  154. package/src/templates/MigrationsGuidePage.tsx +61 -0
  155. package/src/templates/ModuleGuidePage.tsx +71 -0
  156. package/src/templates/SimpleDocPage.tsx +587 -0
  157. package/src/templates/SimpleHomePage.tsx +385 -0
  158. package/src/templates/env.example/.env.example +6 -0
  159. package/src/templates/migrations/20201010100000_app_base.sql +228 -0
@@ -0,0 +1,846 @@
1
+ import fs from "fs-extra";
2
+ import path from "path";
3
+ import { fileURLToPath } from "url";
4
+ import chalk from "chalk";
5
+ import inquirer from "inquirer";
6
+ import { execSync } from "child_process";
7
+ import { AVAILABLE_MODULES } from "./module-add.js";
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+ export async function initApp(options) {
11
+ const { targetDir, force, projectName, useHeroUI, interactive = true, } = options;
12
+ let { withAuth = false } = options;
13
+ console.log(chalk.blue("\n🚀 LastBrain App Init\n"));
14
+ console.log(chalk.gray(`📁 Dossier cible: ${targetDir}`));
15
+ if (useHeroUI) {
16
+ console.log(chalk.magenta(`🎹 Mode: HeroUI\n`));
17
+ }
18
+ else {
19
+ console.log(chalk.gray(`🎹 Mode: Tailwind CSS only\n`));
20
+ }
21
+ // Sélection interactive des modules
22
+ const selectedModules = [];
23
+ if (interactive && !withAuth) {
24
+ console.log(chalk.blue("📩 SĂ©lection des modules:\n"));
25
+ const answers = await inquirer.prompt([
26
+ {
27
+ type: "checkbox",
28
+ name: "modules",
29
+ message: "Quels modules voulez-vous installer ?",
30
+ choices: AVAILABLE_MODULES.map((module) => ({
31
+ name: `${module.displayName} - ${module.description}`,
32
+ value: module.name,
33
+ checked: false,
34
+ })),
35
+ },
36
+ ]);
37
+ selectedModules.push(...answers.modules);
38
+ withAuth = selectedModules.includes("auth");
39
+ console.log();
40
+ }
41
+ // Créer le dossier s'il n'existe pas
42
+ await fs.ensureDir(targetDir);
43
+ // 1. Vérifier/créer package.json
44
+ await ensurePackageJson(targetDir, projectName);
45
+ // 2. Installer les dépendances
46
+ await addDependencies(targetDir, useHeroUI, withAuth);
47
+ // 3. Créer la structure Next.js
48
+ await createNextStructure(targetDir, force, useHeroUI, withAuth);
49
+ // 4. Créer les fichiers de configuration
50
+ await createConfigFiles(targetDir, force, useHeroUI);
51
+ // 5. Créer .gitignore et .env.local.example
52
+ await createGitIgnore(targetDir, force);
53
+ await createEnvExample(targetDir, force);
54
+ // 6. Créer la structure Supabase avec migrations
55
+ await createSupabaseStructure(targetDir, force);
56
+ // 7. Ajouter les scripts NPM
57
+ await addScriptsToPackageJson(targetDir);
58
+ // 8. Enregistrer les modules sélectionnés
59
+ if (withAuth || selectedModules.length > 0) {
60
+ await saveModulesConfig(targetDir, selectedModules, withAuth);
61
+ }
62
+ console.log(chalk.green("\n✅ Application LastBrain initialisĂ©e avec succĂšs!\n"));
63
+ const relativePath = path.relative(process.cwd(), targetDir);
64
+ // Demander si l'utilisateur veut lancer l'installation et le dev server
65
+ const { launchNow } = await inquirer.prompt([
66
+ {
67
+ type: "confirm",
68
+ name: "launchNow",
69
+ message: "Voulez-vous installer les dépendances et lancer le serveur de développement maintenant ?",
70
+ default: true,
71
+ },
72
+ ]);
73
+ if (launchNow) {
74
+ console.log(chalk.yellow("\n📩 Installation des dĂ©pendances...\n"));
75
+ try {
76
+ execSync("pnpm install", { cwd: targetDir, stdio: "inherit" });
77
+ console.log(chalk.green("\n✓ DĂ©pendances installĂ©es\n"));
78
+ console.log(chalk.yellow("🔧 GĂ©nĂ©ration des routes des modules...\n"));
79
+ execSync("pnpm build:modules", { cwd: targetDir, stdio: "inherit" });
80
+ console.log(chalk.green("\n✓ Routes des modules gĂ©nĂ©rĂ©es\n"));
81
+ // Détecter le port (par défaut 3000 pour Next.js)
82
+ const port = 3000;
83
+ const url = `http://127.0.0.1:${port}`;
84
+ console.log(chalk.yellow("🚀 Lancement du serveur de dĂ©veloppement...\n"));
85
+ console.log(chalk.cyan(`đŸ“± Ouvrez votre navigateur sur : ${url}\n`));
86
+ // Ouvrir le navigateur
87
+ const openCommand = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
88
+ setTimeout(() => {
89
+ try {
90
+ execSync(`${openCommand} ${url}`, { stdio: "ignore" });
91
+ }
92
+ catch (error) {
93
+ // Ignorer les erreurs d'ouverture du navigateur
94
+ }
95
+ }, 2000);
96
+ // Lancer pnpm dev
97
+ execSync("pnpm dev", { cwd: targetDir, stdio: "inherit" });
98
+ }
99
+ catch (error) {
100
+ console.error(chalk.red("\n❌ Erreur lors du lancement\n"));
101
+ console.log(chalk.cyan("\nVous pouvez lancer manuellement avec :"));
102
+ console.log(chalk.white(` cd ${relativePath}`));
103
+ console.log(chalk.white(" pnpm install"));
104
+ console.log(chalk.white(" pnpm build:modules"));
105
+ console.log(chalk.white(" pnpm dev\n"));
106
+ }
107
+ }
108
+ else {
109
+ console.log(chalk.cyan("\nProchaines étapes:"));
110
+ console.log(chalk.white(" 1. cd " + relativePath));
111
+ console.log(chalk.white(" 2. pnpm install"));
112
+ console.log(chalk.white(" 3. pnpm db:init (pour initialiser Supabase local)"));
113
+ console.log(chalk.white(" 4. pnpm build:modules (pour générer les routes des modules)"));
114
+ console.log(chalk.white(" 5. pnpm dev (pour démarrer le serveur de développement)\n"));
115
+ // Afficher la commande cd pour faciliter la copie
116
+ console.log(chalk.gray("Pour vous déplacer dans le projet :"));
117
+ console.log(chalk.cyan(` cd ${relativePath}\n`));
118
+ }
119
+ }
120
+ async function ensurePackageJson(targetDir, projectName) {
121
+ const pkgPath = path.join(targetDir, "package.json");
122
+ if (!fs.existsSync(pkgPath)) {
123
+ console.log(chalk.yellow("📩 CrĂ©ation de package.json..."));
124
+ const pkg = {
125
+ name: projectName || path.basename(targetDir),
126
+ version: "0.1.0",
127
+ private: true,
128
+ type: "module",
129
+ scripts: {},
130
+ dependencies: {},
131
+ devDependencies: {},
132
+ };
133
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
134
+ console.log(chalk.green("✓ package.json créé"));
135
+ }
136
+ else {
137
+ console.log(chalk.green("✓ package.json existe"));
138
+ }
139
+ }
140
+ async function addDependencies(targetDir, useHeroUI, withAuth) {
141
+ const pkgPath = path.join(targetDir, "package.json");
142
+ const pkg = await fs.readJson(pkgPath);
143
+ console.log(chalk.yellow("\n📩 Configuration des dĂ©pendances..."));
144
+ // Dependencies
145
+ const requiredDeps = {
146
+ next: "^15.0.3",
147
+ react: "^18.3.1",
148
+ "react-dom": "^18.3.1",
149
+ "@lastbrain/app": "workspace:*",
150
+ "@lastbrain/core": "workspace:*",
151
+ "@lastbrain/ui": "workspace:*",
152
+ "next-themes": "^0.4.6",
153
+ };
154
+ // Ajouter module-auth si demandé
155
+ if (withAuth) {
156
+ requiredDeps["@lastbrain/module-auth"] = "workspace:*";
157
+ }
158
+ // Ajouter les dépendances HeroUI si nécessaire
159
+ if (useHeroUI) {
160
+ // Core
161
+ requiredDeps["@heroui/system"] = "^2.4.23";
162
+ requiredDeps["@heroui/theme"] = "^2.4.23";
163
+ requiredDeps["@heroui/react-utils"] = "^2.1.14";
164
+ requiredDeps["@heroui/framer-utils"] = "^2.1.23";
165
+ // Buttons & Actions
166
+ requiredDeps["@heroui/button"] = "^2.2.27";
167
+ requiredDeps["@heroui/link"] = "^2.2.23";
168
+ // Forms
169
+ requiredDeps["@heroui/input"] = "^2.4.28";
170
+ requiredDeps["@heroui/checkbox"] = "^2.3.27";
171
+ requiredDeps["@heroui/radio"] = "^2.3.27";
172
+ requiredDeps["@heroui/select"] = "^2.4.28";
173
+ requiredDeps["@heroui/switch"] = "^2.2.24";
174
+ requiredDeps["@heroui/form"] = "^2.1.27";
175
+ requiredDeps["@heroui/autocomplete"] = "^2.3.29";
176
+ // Layout
177
+ requiredDeps["@heroui/card"] = "^2.2.25";
178
+ requiredDeps["@heroui/navbar"] = "^2.2.25";
179
+ requiredDeps["@heroui/divider"] = "^2.2.20";
180
+ requiredDeps["@heroui/spacer"] = "^2.2.21";
181
+ // Navigation
182
+ requiredDeps["@heroui/tabs"] = "^2.2.24";
183
+ requiredDeps["@heroui/breadcrumbs"] = "^2.2.19";
184
+ requiredDeps["@heroui/pagination"] = "^2.2.24";
185
+ requiredDeps["@heroui/listbox"] = "^2.3.26";
186
+ // Feedback
187
+ requiredDeps["@heroui/spinner"] = "^2.2.24";
188
+ requiredDeps["@heroui/progress"] = "^2.2.22";
189
+ requiredDeps["@heroui/skeleton"] = "^2.2.17";
190
+ requiredDeps["@heroui/alert"] = "^2.2.27";
191
+ requiredDeps["@heroui/toast"] = "^2.0.17";
192
+ // Overlays
193
+ requiredDeps["@heroui/modal"] = "^2.2.24";
194
+ requiredDeps["@heroui/tooltip"] = "^2.2.24";
195
+ requiredDeps["@heroui/popover"] = "^2.3.27";
196
+ requiredDeps["@heroui/dropdown"] = "^2.3.27";
197
+ requiredDeps["@heroui/drawer"] = "^2.2.24";
198
+ // Data Display
199
+ requiredDeps["@heroui/avatar"] = "^2.2.22";
200
+ requiredDeps["@heroui/badge"] = "^2.2.17";
201
+ requiredDeps["@heroui/chip"] = "^2.2.22";
202
+ requiredDeps["@heroui/code"] = "^2.2.21";
203
+ requiredDeps["@heroui/image"] = "^2.2.17";
204
+ requiredDeps["@heroui/kbd"] = "^2.2.22";
205
+ requiredDeps["@heroui/snippet"] = "^2.2.28";
206
+ requiredDeps["@heroui/table"] = "^2.2.27";
207
+ requiredDeps["@heroui/user"] = "^2.2.22";
208
+ requiredDeps["@heroui/accordion"] = "^2.2.24";
209
+ // Utilities
210
+ requiredDeps["@heroui/scroll-shadow"] = "^2.3.18";
211
+ requiredDeps["@react-aria/ssr"] = "^3.9.10";
212
+ requiredDeps["@react-aria/visually-hidden"] = "^3.8.28";
213
+ // Dependencies
214
+ requiredDeps["lucide-react"] = "^0.554.0";
215
+ requiredDeps["framer-motion"] = "^11.18.2";
216
+ requiredDeps["clsx"] = "^2.1.1";
217
+ }
218
+ // DevDependencies
219
+ const requiredDevDeps = {
220
+ "@tailwindcss/postcss": "^4.0.0",
221
+ tailwindcss: "^4.0.0",
222
+ typescript: "^5.4.0",
223
+ "@types/node": "^20.0.0",
224
+ "@types/react": "^18.3.0",
225
+ "@types/react-dom": "^18.3.0",
226
+ };
227
+ pkg.dependencies = { ...pkg.dependencies, ...requiredDeps };
228
+ pkg.devDependencies = { ...pkg.devDependencies, ...requiredDevDeps };
229
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
230
+ console.log(chalk.green("✓ DĂ©pendances configurĂ©es"));
231
+ }
232
+ async function createNextStructure(targetDir, force, useHeroUI, withAuth) {
233
+ console.log(chalk.yellow("\n📁 CrĂ©ation de la structure Next.js..."));
234
+ const appDir = path.join(targetDir, "app");
235
+ const stylesDir = path.join(targetDir, "styles");
236
+ await fs.ensureDir(appDir);
237
+ await fs.ensureDir(stylesDir);
238
+ // Générer le layout principal
239
+ const layoutDest = path.join(appDir, "layout.tsx");
240
+ if (!fs.existsSync(layoutDest) || force) {
241
+ let layoutContent = "";
242
+ if (useHeroUI) {
243
+ // Layout avec HeroUI
244
+ layoutContent = `// GENERATED BY LASTBRAIN APP-SHELL
245
+
246
+ "use client";
247
+
248
+ import "../styles/globals.css";
249
+ import { HeroUIProvider } from "@heroui/system";
250
+
251
+ import { ThemeProvider } from "next-themes";
252
+ import { useRouter } from "next/navigation";
253
+ import { AppProviders } from "@lastbrain/app";
254
+ import type { PropsWithChildren } from "react";
255
+ import { AppHeader } from "../components/AppHeader";
256
+
257
+ export default function RootLayout({ children }: PropsWithChildren<{}>) {
258
+ const router = useRouter();
259
+
260
+ return (
261
+ <html lang="fr" suppressHydrationWarning>
262
+ <body className="min-h-screen">
263
+ <HeroUIProvider navigate={router.push}>
264
+ <ThemeProvider
265
+ attribute="class"
266
+ defaultTheme="dark"
267
+ enableSystem={false}
268
+ storageKey="lastbrain-theme"
269
+ >
270
+ <AppProviders>
271
+ <AppHeader />
272
+ <div className="min-h-screen text-foreground bg-background">
273
+ {children}
274
+ </div>
275
+ </AppProviders>
276
+ </ThemeProvider>
277
+ </HeroUIProvider>
278
+ </body>
279
+ </html>
280
+ );
281
+ }
282
+ `;
283
+ }
284
+ else {
285
+ // Layout Tailwind CSS uniquement
286
+ layoutContent = `// GENERATED BY LASTBRAIN APP-SHELL
287
+
288
+ "use client";
289
+
290
+ import "../styles/globals.css";
291
+ import { ThemeProvider } from "next-themes";
292
+ import { AppProviders } from "@lastbrain/app";
293
+ import type { PropsWithChildren } from "react";
294
+ import { AppHeader } from "../components/AppHeader";
295
+
296
+ export default function RootLayout({ children }: PropsWithChildren<{}>) {
297
+ return (
298
+ <html lang="fr" suppressHydrationWarning>
299
+ <body className="min-h-screen">
300
+ <ThemeProvider
301
+ attribute="class"
302
+ defaultTheme="light"
303
+ enableSystem={false}
304
+ storageKey="lastbrain-theme"
305
+ >
306
+ <AppProviders>
307
+ <AppHeader />
308
+ <div className="min-h-screen bg-slate-50 text-slate-900 dark:bg-slate-950 dark:text-white">
309
+ {children}
310
+ </div>
311
+ </AppProviders>
312
+ </ThemeProvider>
313
+ </body>
314
+ </html>
315
+ );
316
+ }
317
+ `;
318
+ }
319
+ await fs.writeFile(layoutDest, layoutContent);
320
+ console.log(chalk.green("✓ app/layout.tsx créé"));
321
+ }
322
+ else {
323
+ console.log(chalk.gray(" app/layout.tsx existe déjà (utilisez --force pour écraser)"));
324
+ }
325
+ // Créer globals.css
326
+ const globalsPath = path.join(stylesDir, "globals.css");
327
+ if (!fs.existsSync(globalsPath) || force) {
328
+ const globalsContent = `@config "../tailwind.config.mjs";
329
+ @import "tailwindcss";
330
+ `;
331
+ await fs.writeFile(globalsPath, globalsContent);
332
+ console.log(chalk.green("✓ styles/globals.css créé"));
333
+ }
334
+ // Créer la page d'accueil publique (racine)
335
+ const homePagePath = path.join(appDir, "page.tsx");
336
+ if (!fs.existsSync(homePagePath) || force) {
337
+ const homePageContent = `// GENERATED BY LASTBRAIN APP-SHELL
338
+
339
+ import { SimpleHomePage } from "@lastbrain/app";
340
+
341
+ export default function RootPage() {
342
+ return <SimpleHomePage showAuth={${withAuth}} />;
343
+ }
344
+ `;
345
+ await fs.writeFile(homePagePath, homePageContent);
346
+ console.log(chalk.green("✓ app/page.tsx créé"));
347
+ }
348
+ // Créer la page not-found.tsx
349
+ const notFoundPath = path.join(appDir, "not-found.tsx");
350
+ if (!fs.existsSync(notFoundPath) || force) {
351
+ const notFoundContent = `"use client";
352
+ import { Button } from "@lastbrain/ui";
353
+ import { useRouter } from "next/navigation";
354
+
355
+ export default function NotFound() {
356
+ const router = useRouter();
357
+ return (
358
+ <div className="flex min-h-screen items-center justify-center bg-background">
359
+ <div className="mx-auto max-w-md text-center">
360
+ <h1 className="mb-4 text-6xl font-bold text-foreground">404</h1>
361
+ <h2 className="mb-4 text-2xl font-semibold text-foreground">
362
+ Page non trouvée
363
+ </h2>
364
+ <p className="mb-8 text-muted-foreground">
365
+ La page que vous recherchez n'existe pas ou a été déplacée.
366
+ </p>
367
+ <Button
368
+ onPress={() => {
369
+ router.back();
370
+ }}
371
+ className="inline-flex items-center justify-center rounded-md bg-primary px-6 py-3 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90"
372
+ >
373
+ Retour Ă  l'accueil
374
+ </Button>
375
+ </div>
376
+ </div>
377
+ );
378
+ }
379
+ `;
380
+ await fs.writeFile(notFoundPath, notFoundContent);
381
+ console.log(chalk.green("✓ app/not-found.tsx créé"));
382
+ }
383
+ // Créer les routes avec leurs layouts
384
+ await createRoute(appDir, "admin", "admin", force);
385
+ await createRoute(appDir, "docs", "public", force);
386
+ // Créer le composant AppHeader
387
+ await createAppHeader(targetDir, force);
388
+ }
389
+ async function createRoute(appDir, routeName, layoutType, force) {
390
+ const routeDir = path.join(appDir, routeName);
391
+ await fs.ensureDir(routeDir);
392
+ // Layout pour la route
393
+ const layoutPath = path.join(routeDir, "layout.tsx");
394
+ if (!fs.existsSync(layoutPath) || force) {
395
+ const layoutComponent = layoutType.charAt(0).toUpperCase() + layoutType.slice(1) + "Layout";
396
+ const layoutContent = `import { ${layoutComponent} } from "@lastbrain/app";
397
+
398
+ export default ${layoutComponent};
399
+ `;
400
+ await fs.writeFile(layoutPath, layoutContent);
401
+ console.log(chalk.green(`✓ app/${routeName}/layout.tsx créé`));
402
+ }
403
+ // Page par défaut
404
+ const pagePath = path.join(routeDir, "page.tsx");
405
+ if (!fs.existsSync(pagePath) || force) {
406
+ let templateImport = "";
407
+ let componentName = null;
408
+ // Choisir le template approprié selon la route
409
+ switch (routeName) {
410
+ case "admin":
411
+ templateImport = 'import { ModuleGuidePage } from "@lastbrain/app";';
412
+ componentName = "ModuleGuidePage";
413
+ break;
414
+ case "docs":
415
+ templateImport = 'import { SimpleDocPage } from "@lastbrain/app";';
416
+ componentName = "SimpleDocPage";
417
+ break;
418
+ default:
419
+ // Template générique pour les autres routes
420
+ templateImport = `// Generic page for ${routeName}`;
421
+ componentName = null;
422
+ }
423
+ const pageContent = componentName
424
+ ? `// GENERATED BY LASTBRAIN APP-SHELL
425
+
426
+ ${templateImport}
427
+
428
+ export default function ${routeName.charAt(0).toUpperCase() + routeName.slice(1)}Page() {
429
+ return <${componentName} />;
430
+ }
431
+ `
432
+ : `// GENERATED BY LASTBRAIN APP-SHELL
433
+
434
+ export default function ${routeName.charAt(0).toUpperCase() + routeName.slice(1)}Page() {
435
+ return (
436
+ <div className="container mx-auto px-4 py-8">
437
+ <h1 className="text-3xl font-bold mb-4">${routeName.charAt(0).toUpperCase() + routeName.slice(1)} Page</h1>
438
+ <p className="text-slate-600 dark:text-slate-400">
439
+ Cette page a été générée par LastBrain Init.
440
+ </p>
441
+ <p className="text-sm text-slate-500 dark:text-slate-500 mt-4">
442
+ Route: /${routeName}
443
+ </p>
444
+ </div>
445
+ );
446
+ }
447
+ `;
448
+ await fs.writeFile(pagePath, pageContent);
449
+ console.log(chalk.green(`✓ app/${routeName}/page.tsx créé`));
450
+ }
451
+ }
452
+ async function createAppHeader(targetDir, force) {
453
+ const componentsDir = path.join(targetDir, "components");
454
+ await fs.ensureDir(componentsDir);
455
+ const headerPath = path.join(componentsDir, "AppHeader.tsx");
456
+ if (!fs.existsSync(headerPath) || force) {
457
+ const headerContent = `"use client";
458
+
459
+ import { Header } from "@lastbrain/ui";
460
+ import { menuConfig } from "../config/menu";
461
+ import { supabaseBrowserClient } from "@lastbrain/core";
462
+ import { useRouter } from "next/navigation";
463
+ import { useAuthSession } from "@lastbrain/app";
464
+
465
+ export function AppHeader() {
466
+ const router = useRouter();
467
+ const { user, isSuperAdmin } = useAuthSession();
468
+
469
+ const handleLogout = async () => {
470
+ await supabaseBrowserClient.auth.signOut();
471
+ router.push("/");
472
+ router.refresh();
473
+ };
474
+
475
+ return (
476
+ <Header
477
+ user={user}
478
+ onLogout={handleLogout}
479
+ menuConfig={menuConfig}
480
+ brandName="LastBrain App"
481
+ brandHref="/"
482
+ isSuperAdmin={isSuperAdmin}
483
+ />
484
+ );
485
+ }
486
+ `;
487
+ await fs.writeFile(headerPath, headerContent);
488
+ console.log(chalk.green("✓ components/AppHeader.tsx créé"));
489
+ }
490
+ else {
491
+ console.log(chalk.gray(" components/AppHeader.tsx existe déjà (utilisez --force pour écraser)"));
492
+ }
493
+ }
494
+ async function createConfigFiles(targetDir, force, useHeroUI) {
495
+ console.log(chalk.yellow("\n⚙ CrĂ©ation des fichiers de configuration..."));
496
+ // middleware.ts - Protection des routes /auth/* et /admin/*
497
+ const middlewarePath = path.join(targetDir, "middleware.ts");
498
+ if (!fs.existsSync(middlewarePath) || force) {
499
+ const middleware = `import { type NextRequest, NextResponse } from "next/server";
500
+ import { createMiddlewareClient } from "@lastbrain/core/server";
501
+
502
+ export async function middleware(request: NextRequest) {
503
+ const { pathname } = request.nextUrl;
504
+
505
+ // Protéger les routes /auth/* (espace membre)
506
+ if (pathname.startsWith("/auth")) {
507
+ try {
508
+ const { supabase, response } = createMiddlewareClient(request);
509
+ const {
510
+ data: { session },
511
+ } = await supabase.auth.getSession();
512
+
513
+ // Pas de session → redirection vers /signin
514
+ if (!session) {
515
+ const redirectUrl = new URL("/signin", request.url);
516
+ redirectUrl.searchParams.set("redirect", pathname);
517
+ return NextResponse.redirect(redirectUrl);
518
+ }
519
+
520
+ return response;
521
+ } catch (error) {
522
+ console.error("Middleware auth error:", error);
523
+ return NextResponse.redirect(new URL("/signin", request.url));
524
+ }
525
+ }
526
+
527
+ // Protéger les routes /admin/* (superadmin uniquement)
528
+ if (pathname.startsWith("/admin")) {
529
+ try {
530
+ const { supabase, response } = createMiddlewareClient(request);
531
+ const {
532
+ data: { session },
533
+ } = await supabase.auth.getSession();
534
+
535
+ // Pas de session → redirection vers /signin
536
+ if (!session) {
537
+ const redirectUrl = new URL("/signin", request.url);
538
+ redirectUrl.searchParams.set("redirect", pathname);
539
+ return NextResponse.redirect(redirectUrl);
540
+ }
541
+
542
+ // Vérifier si l'utilisateur est superadmin
543
+ const { data: isSuperAdmin, error } = await supabase.rpc(
544
+ "is_superadmin",
545
+ { user_id: session.user.id }
546
+ );
547
+
548
+ if (error || !isSuperAdmin) {
549
+ console.error("Access denied: not a superadmin", error);
550
+ return NextResponse.redirect(new URL("/", request.url));
551
+ }
552
+
553
+ return response;
554
+ } catch (error) {
555
+ console.error("Middleware admin error:", error);
556
+ return NextResponse.redirect(new URL("/", request.url));
557
+ }
558
+ }
559
+
560
+ return NextResponse.next();
561
+ }
562
+
563
+ export const config = {
564
+ matcher: [
565
+ /*
566
+ * Match all request paths except:
567
+ * - _next/static (static files)
568
+ * - _next/image (image optimization files)
569
+ * - favicon.ico (favicon file)
570
+ * - public folder
571
+ */
572
+ "/((?!_next/static|_next/image|favicon.ico|.*\\\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
573
+ ],
574
+ };
575
+ `;
576
+ await fs.writeFile(middlewarePath, middleware);
577
+ console.log(chalk.green("✓ middleware.ts créé (protection /auth/* et /admin/*)"));
578
+ }
579
+ // next.config.mjs
580
+ const nextConfigPath = path.join(targetDir, "next.config.mjs");
581
+ if (!fs.existsSync(nextConfigPath) || force) {
582
+ const nextConfig = `/** @type {import('next').NextConfig} */
583
+ const nextConfig = {
584
+ reactStrictMode: true,
585
+ };
586
+
587
+ export default nextConfig;
588
+ `;
589
+ await fs.writeFile(nextConfigPath, nextConfig);
590
+ console.log(chalk.green("✓ next.config.mjs créé"));
591
+ }
592
+ // tailwind.config.mjs
593
+ const tailwindConfigPath = path.join(targetDir, "tailwind.config.mjs");
594
+ if (!fs.existsSync(tailwindConfigPath) || force) {
595
+ let tailwindConfig = "";
596
+ if (useHeroUI) {
597
+ // Configuration avec HeroUI
598
+ tailwindConfig = `import {heroui} from "@heroui/theme"
599
+
600
+ /** @type {import('tailwindcss').Config} */
601
+ const config = {
602
+ content: [
603
+ "./app/**/*.{js,ts,jsx,tsx,mdx}",
604
+ "./components/**/*.{js,ts,jsx,tsx,mdx}",
605
+ "./node_modules/@lastbrain/*/dist/**/*.js",
606
+ "./node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}"
607
+ ],
608
+ theme: {
609
+ extend: {},
610
+ },
611
+ darkMode: "class",
612
+ plugins: [heroui()],
613
+ }
614
+
615
+ export default config;
616
+ `;
617
+ }
618
+ else {
619
+ // Configuration Tailwind CSS uniquement
620
+ tailwindConfig = `module.exports = {
621
+ content: [
622
+ "./app/**/*.{js,ts,jsx,tsx,mdx}",
623
+ "./components/**/*.{js,ts,jsx,tsx,mdx}",
624
+ "./node_modules/@lastbrain/*/dist/**/*.js",
625
+ ],
626
+ theme: {
627
+ extend: {
628
+ colors: {
629
+ primary: {
630
+ 50: "#eff6ff",
631
+ 100: "#dbeafe",
632
+ 200: "#bfdbfe",
633
+ 300: "#93c5fd",
634
+ 400: "#60a5fa",
635
+ 500: "#3b82f6",
636
+ 600: "#2563eb",
637
+ 700: "#1d4ed8",
638
+ 800: "#1e40af",
639
+ 900: "#1e3a8a",
640
+ 950: "#172554",
641
+ },
642
+ },
643
+ },
644
+ },
645
+ plugins: [],
646
+ };
647
+ `;
648
+ }
649
+ await fs.writeFile(tailwindConfigPath, tailwindConfig);
650
+ console.log(chalk.green("✓ tailwind.config.mjs créé"));
651
+ }
652
+ // postcss.config.mjs
653
+ const postcssConfigPath = path.join(targetDir, "postcss.config.mjs");
654
+ if (!fs.existsSync(postcssConfigPath) || force) {
655
+ const postcssConfig = `const config = {
656
+ plugins: {
657
+ '@tailwindcss/postcss': {},
658
+ autoprefixer: {},
659
+ },
660
+ };
661
+
662
+ export default config;
663
+ `;
664
+ await fs.writeFile(postcssConfigPath, postcssConfig);
665
+ console.log(chalk.green("✓ postcss.config.mjs créé"));
666
+ }
667
+ // tsconfig.json
668
+ const tsconfigPath = path.join(targetDir, "tsconfig.json");
669
+ if (!fs.existsSync(tsconfigPath) || force) {
670
+ const tsconfig = {
671
+ compilerOptions: {
672
+ target: "ES2020",
673
+ lib: ["ES2020", "DOM", "DOM.Iterable"],
674
+ jsx: "preserve",
675
+ module: "ESNext",
676
+ moduleResolution: "bundler",
677
+ resolveJsonModule: true,
678
+ allowJs: true,
679
+ strict: true,
680
+ noEmit: true,
681
+ esModuleInterop: true,
682
+ skipLibCheck: true,
683
+ forceConsistentCasingInFileNames: true,
684
+ incremental: true,
685
+ isolatedModules: true,
686
+ plugins: [{ name: "next" }],
687
+ paths: {
688
+ "@/*": ["./*"],
689
+ },
690
+ },
691
+ include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
692
+ exclude: ["node_modules"],
693
+ };
694
+ await fs.writeJson(tsconfigPath, tsconfig, { spaces: 2 });
695
+ console.log(chalk.green("✓ tsconfig.json créé"));
696
+ }
697
+ }
698
+ async function createGitIgnore(targetDir, force) {
699
+ const gitignorePath = path.join(targetDir, ".gitignore");
700
+ if (!fs.existsSync(gitignorePath) || force) {
701
+ console.log(chalk.yellow("\n📝 CrĂ©ation de .gitignore..."));
702
+ const gitignoreContent = `# Dependencies
703
+ node_modules/
704
+ .pnp
705
+ .pnp.js
706
+
707
+ # Testing
708
+ coverage/
709
+
710
+ # Next.js
711
+ .next/
712
+ out/
713
+ build/
714
+ dist/
715
+
716
+ # Production
717
+ *.log*
718
+
719
+ # Misc
720
+ .DS_Store
721
+ *.pem
722
+
723
+ # Debug
724
+ npm-debug.log*
725
+ yarn-debug.log*
726
+ yarn-error.log*
727
+
728
+ # Local env files
729
+ .env
730
+ .env*.local
731
+ .env.production
732
+
733
+ # Vercel
734
+ .vercel
735
+
736
+ # Typescript
737
+ *.tsbuildinfo
738
+ next-env.d.ts
739
+
740
+ # Supabase
741
+ supabase/.temp/
742
+ supabase/.branches/
743
+
744
+ # LastBrain generated
745
+ **/navigation.generated.ts
746
+ **/routes.generated.ts
747
+ `;
748
+ await fs.writeFile(gitignorePath, gitignoreContent);
749
+ console.log(chalk.green("✓ .gitignore créé"));
750
+ }
751
+ }
752
+ async function createEnvExample(targetDir, force) {
753
+ const envExamplePath = path.join(targetDir, ".env.local.example");
754
+ if (!fs.existsSync(envExamplePath) || force) {
755
+ console.log(chalk.yellow("\n🔐 CrĂ©ation de .env.local.example..."));
756
+ const envContent = `# Supabase Configuration
757
+ # Exécutez 'pnpm db:init' pour initialiser Supabase local et générer le vrai .env.local
758
+
759
+ # Supabase Local (par défaut)
760
+ NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321
761
+ NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_LOCAL_ANON_KEY
762
+ SUPABASE_SERVICE_ROLE_KEY=YOUR_LOCAL_SERVICE_ROLE_KEY
763
+
764
+ # Supabase Production
765
+ # NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
766
+ # NEXT_PUBLIC_SUPABASE_ANON_KEY=your_production_anon_key
767
+ # SUPABASE_SERVICE_ROLE_KEY=your_production_service_role_key
768
+ `;
769
+ await fs.writeFile(envExamplePath, envContent);
770
+ console.log(chalk.green("✓ .env.local.example créé"));
771
+ }
772
+ }
773
+ async function createSupabaseStructure(targetDir, force) {
774
+ console.log(chalk.yellow("\nđŸ—„ïž CrĂ©ation de la structure Supabase..."));
775
+ const supabaseDir = path.join(targetDir, "supabase");
776
+ const migrationsDir = path.join(supabaseDir, "migrations");
777
+ await fs.ensureDir(migrationsDir);
778
+ // Copier le fichier de migration initial depuis le template
779
+ const templateMigrationPath = path.join(__dirname, "../templates/migrations/20201010100000_app_base.sql");
780
+ const migrationDestPath = path.join(migrationsDir, "20201010100000_app_base.sql");
781
+ if (!fs.existsSync(migrationDestPath) || force) {
782
+ try {
783
+ if (fs.existsSync(templateMigrationPath)) {
784
+ await fs.copy(templateMigrationPath, migrationDestPath);
785
+ console.log(chalk.green("✓ supabase/migrations/20201010100000_app_base.sql créé"));
786
+ }
787
+ else {
788
+ console.log(chalk.yellow("⚠ Template de migration introuvable, crĂ©ation d'un fichier vide"));
789
+ await fs.writeFile(migrationDestPath, "-- Initial migration\n-- Add your database schema here\n");
790
+ console.log(chalk.green("✓ supabase/migrations/20201010100000_app_base.sql créé (vide)"));
791
+ }
792
+ }
793
+ catch (error) {
794
+ console.error(chalk.red("✗ Erreur lors de la crĂ©ation de la migration:"), error);
795
+ }
796
+ }
797
+ else {
798
+ console.log(chalk.gray(" supabase/migrations/20201010100000_app_base.sql existe déjà"));
799
+ }
800
+ }
801
+ async function addScriptsToPackageJson(targetDir) {
802
+ console.log(chalk.yellow("\n🔧 Ajout des scripts NPM..."));
803
+ const pkgPath = path.join(targetDir, "package.json");
804
+ const pkg = await fs.readJson(pkgPath);
805
+ const scripts = {
806
+ dev: "next dev",
807
+ build: "next build",
808
+ start: "next start",
809
+ lint: "next lint",
810
+ lastbrain: "node node_modules/@lastbrain/app/dist/cli.js",
811
+ "build:modules": "PROJECT_ROOT=$PWD pnpm --filter @lastbrain/app module:build",
812
+ "db:migrations:sync": "PROJECT_ROOT=$PWD pnpm --filter @lastbrain/app db:migrations:sync",
813
+ "db:init": "PROJECT_ROOT=$PWD pnpm --filter @lastbrain/app db:init",
814
+ "readme:create": "PROJECT_ROOT=$PWD pnpm --filter @lastbrain/app readme:create",
815
+ };
816
+ pkg.scripts = { ...pkg.scripts, ...scripts };
817
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
818
+ console.log(chalk.green("✓ Scripts ajoutĂ©s au package.json"));
819
+ }
820
+ async function saveModulesConfig(targetDir, selectedModules, withAuth) {
821
+ const modulesConfigPath = path.join(targetDir, ".lastbrain", "modules.json");
822
+ await fs.ensureDir(path.dirname(modulesConfigPath));
823
+ const modules = [];
824
+ // Ajouter TOUS les modules disponibles
825
+ for (const availableModule of AVAILABLE_MODULES) {
826
+ const isSelected = selectedModules.includes(availableModule.name) || (withAuth && availableModule.name === 'auth');
827
+ // Vérifier si le module a des migrations
828
+ const modulePath = path.join(targetDir, "node_modules", ...availableModule.package.split("/"));
829
+ const migrationsDir = path.join(modulePath, "supabase", "migrations");
830
+ const moduleConfig = {
831
+ package: availableModule.package,
832
+ active: isSelected,
833
+ };
834
+ if (fs.existsSync(migrationsDir)) {
835
+ const migrationFiles = fs
836
+ .readdirSync(migrationsDir)
837
+ .filter((f) => f.endsWith(".sql"));
838
+ if (migrationFiles.length > 0) {
839
+ moduleConfig.migrations = isSelected ? migrationFiles : [];
840
+ }
841
+ }
842
+ modules.push(moduleConfig);
843
+ }
844
+ await fs.writeJson(modulesConfigPath, { modules }, { spaces: 2 });
845
+ console.log(chalk.green("✓ Configuration des modules sauvegardĂ©e"));
846
+ }