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