@lastbrain/app 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/scripts/init-app.js +3 -3
- package/dist/styles.css +2 -0
- package/package.json +11 -3
- package/src/app-shell/(admin)/layout.tsx +13 -0
- package/src/app-shell/(auth)/layout.tsx +13 -0
- package/src/app-shell/(public)/page.tsx +11 -0
- package/src/app-shell/layout.tsx +5 -0
- package/src/app-shell/not-found.tsx +28 -0
- package/src/auth/authHelpers.ts +24 -0
- package/src/auth/useAuthSession.ts +54 -0
- package/src/cli.ts +96 -0
- package/src/index.ts +21 -0
- package/src/layouts/AdminLayout.tsx +7 -0
- package/src/layouts/AppProviders.tsx +61 -0
- package/src/layouts/AuthLayout.tsx +7 -0
- package/src/layouts/PublicLayout.tsx +7 -0
- package/src/layouts/RootLayout.tsx +27 -0
- package/src/modules/module-loader.ts +14 -0
- package/src/scripts/README.md +262 -0
- package/src/scripts/db-init.ts +338 -0
- package/src/scripts/db-migrations-sync.ts +86 -0
- package/src/scripts/dev-sync.ts +218 -0
- package/src/scripts/init-app.ts +1077 -0
- package/src/scripts/module-add.ts +242 -0
- package/src/scripts/module-build.ts +502 -0
- package/src/scripts/module-create.ts +809 -0
- package/src/scripts/module-list.ts +37 -0
- package/src/scripts/module-remove.ts +367 -0
- package/src/scripts/readme-build.ts +60 -0
- package/src/styles.css +3 -0
- package/src/templates/AuthGuidePage.tsx +68 -0
- package/src/templates/DefaultDoc.tsx +462 -0
- package/src/templates/DocPage.tsx +381 -0
- package/src/templates/DocsPageWithModules.tsx +22 -0
- package/src/templates/MigrationsGuidePage.tsx +61 -0
- package/src/templates/ModuleGuidePage.tsx +71 -0
- package/src/templates/SimpleDocPage.tsx +587 -0
- package/src/templates/SimpleHomePage.tsx +385 -0
- package/src/templates/env.example/.env.example +6 -0
- package/src/templates/migrations/20201010100000_app_base.sql +228 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# Scripts LastBrain
|
|
2
|
+
|
|
3
|
+
Ce répertoire contient tous les scripts utilitaires pour gérer les applications et modules LastBrain.
|
|
4
|
+
|
|
5
|
+
## Scripts disponibles
|
|
6
|
+
|
|
7
|
+
### `init-app.ts`
|
|
8
|
+
|
|
9
|
+
Initialise une nouvelle application Next.js LastBrain avec toute la structure nécessaire.
|
|
10
|
+
|
|
11
|
+
**Usage :**
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm lastbrain init [directory] [options]
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**Options :**
|
|
18
|
+
|
|
19
|
+
- `--force` : Écrase les fichiers existants
|
|
20
|
+
- `--heroui` : Utilise HeroUI au lieu de NextUI
|
|
21
|
+
- `--with-auth` : Inclut le module d'authentification
|
|
22
|
+
- `--no-interactive` : Mode non-interactif
|
|
23
|
+
|
|
24
|
+
### `module-create.ts`
|
|
25
|
+
|
|
26
|
+
Crée un nouveau module dans `packages/module-{nom}` avec toute la structure nécessaire.
|
|
27
|
+
|
|
28
|
+
**Usage :**
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pnpm lastbrain create-module
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Fonctionnalités :**
|
|
35
|
+
|
|
36
|
+
- Génération interactive du module
|
|
37
|
+
- Création des pages (public/auth/admin)
|
|
38
|
+
- Création des routes API CRUD
|
|
39
|
+
- Génération des migrations SQL
|
|
40
|
+
- Configuration complète (package.json, tsconfig.json, build.config.ts)
|
|
41
|
+
|
|
42
|
+
**Voir :** [MODULE_CREATION.md](../../../../docs/MODULE_CREATION.md)
|
|
43
|
+
|
|
44
|
+
### `module-add.ts`
|
|
45
|
+
|
|
46
|
+
Ajoute un module existant à l'application courante.
|
|
47
|
+
|
|
48
|
+
**Usage :**
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pnpm lastbrain add-module <nom-du-module>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Exemple :**
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pnpm lastbrain add-module auth
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Actions effectuées :**
|
|
61
|
+
|
|
62
|
+
- Ajout de la dépendance au package.json
|
|
63
|
+
- Installation via pnpm
|
|
64
|
+
- Copie des migrations du module
|
|
65
|
+
- Application des migrations (avec choix reset/push/skip)
|
|
66
|
+
- Enregistrement dans `.lastbrain/modules.json`
|
|
67
|
+
|
|
68
|
+
### `module-remove.ts`
|
|
69
|
+
|
|
70
|
+
Supprime un module de l'application courante.
|
|
71
|
+
|
|
72
|
+
**Usage :**
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pnpm lastbrain remove-module <nom-du-module>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Actions effectuées :**
|
|
79
|
+
|
|
80
|
+
- Suppression de la dépendance du package.json
|
|
81
|
+
- Suppression du module de `.lastbrain/modules.json`
|
|
82
|
+
- Nettoyage via pnpm
|
|
83
|
+
|
|
84
|
+
**Note :** Les migrations ne sont pas automatiquement inversées.
|
|
85
|
+
|
|
86
|
+
### `module-list.ts`
|
|
87
|
+
|
|
88
|
+
Liste tous les modules disponibles et ceux installés dans l'application courante.
|
|
89
|
+
|
|
90
|
+
**Usage :**
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
pnpm lastbrain list-modules
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Affiche :**
|
|
97
|
+
|
|
98
|
+
- ✅ Modules installés
|
|
99
|
+
- 📦 Modules disponibles
|
|
100
|
+
|
|
101
|
+
### `module-build.ts`
|
|
102
|
+
|
|
103
|
+
Génère automatiquement les routes et pages de l'application à partir des modules installés.
|
|
104
|
+
|
|
105
|
+
**Usage :**
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
pnpm build:modules
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Actions effectuées :**
|
|
112
|
+
|
|
113
|
+
- Lecture des `build.config.ts` de tous les modules installés
|
|
114
|
+
- Génération de `app/navigation.generated.ts`
|
|
115
|
+
- Création des pages dans les sections appropriées (public/auth/admin)
|
|
116
|
+
- Création des routes API
|
|
117
|
+
|
|
118
|
+
**Note :** Utilisé par le script `dev:sync` en mode watch.
|
|
119
|
+
|
|
120
|
+
### `db-init.ts`
|
|
121
|
+
|
|
122
|
+
Initialise la base de données Supabase locale.
|
|
123
|
+
|
|
124
|
+
**Usage :**
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
pnpm db:init
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Actions effectuées :**
|
|
131
|
+
|
|
132
|
+
- Démarre Supabase local (si pas déjà démarré)
|
|
133
|
+
- Génère le fichier `.env.local` avec les clés Supabase
|
|
134
|
+
- Applique les migrations de base
|
|
135
|
+
- Reset de la base de données
|
|
136
|
+
|
|
137
|
+
### `db-migrations-sync.ts`
|
|
138
|
+
|
|
139
|
+
Synchronise les migrations de tous les modules avec la base de données.
|
|
140
|
+
|
|
141
|
+
**Usage :**
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
pnpm db:migrations:sync
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Actions effectuées :**
|
|
148
|
+
|
|
149
|
+
- Collecte toutes les migrations des modules installés
|
|
150
|
+
- Copie dans `supabase/migrations/`
|
|
151
|
+
- Applique les migrations (via `supabase db push` ou `supabase db reset`)
|
|
152
|
+
|
|
153
|
+
### `dev-sync.ts`
|
|
154
|
+
|
|
155
|
+
Lance le mode développement avec synchronisation automatique des modules.
|
|
156
|
+
|
|
157
|
+
**Usage :**
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
pnpm dev:sync
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Actions effectuées :**
|
|
164
|
+
|
|
165
|
+
- Watch des changements dans les modules
|
|
166
|
+
- Régénération automatique des routes
|
|
167
|
+
- Synchronisation des migrations
|
|
168
|
+
- Redémarrage automatique du serveur Next.js
|
|
169
|
+
|
|
170
|
+
### `readme-build.ts`
|
|
171
|
+
|
|
172
|
+
Génère la documentation README.md de l'application.
|
|
173
|
+
|
|
174
|
+
**Usage :**
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
pnpm readme:create
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Actions effectuées :**
|
|
181
|
+
|
|
182
|
+
- Analyse la structure de l'application
|
|
183
|
+
- Collecte les informations des modules installés
|
|
184
|
+
- Génère un README complet avec la documentation
|
|
185
|
+
|
|
186
|
+
## Architecture des scripts
|
|
187
|
+
|
|
188
|
+
### Dépendances communes
|
|
189
|
+
|
|
190
|
+
Tous les scripts utilisent :
|
|
191
|
+
|
|
192
|
+
- `fs-extra` : Gestion des fichiers
|
|
193
|
+
- `chalk` : Affichage coloré dans le terminal
|
|
194
|
+
- `inquirer` : Prompts interactifs (pour certains scripts)
|
|
195
|
+
- `commander` : Parsing des arguments CLI
|
|
196
|
+
|
|
197
|
+
### Structure type d'un script
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
import fs from "fs-extra";
|
|
201
|
+
import path from "path";
|
|
202
|
+
import chalk from "chalk";
|
|
203
|
+
|
|
204
|
+
export async function monScript(targetDir: string, options?: any) {
|
|
205
|
+
console.log(chalk.blue("\n🚀 Début du script...\n"));
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
// Logique du script
|
|
209
|
+
console.log(chalk.green("✅ Succès!"));
|
|
210
|
+
} catch (error) {
|
|
211
|
+
console.error(chalk.red("❌ Erreur:"), error);
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Ajouter un nouveau script
|
|
218
|
+
|
|
219
|
+
1. Créer le fichier TypeScript dans `src/scripts/`
|
|
220
|
+
2. Exporter une fonction principale
|
|
221
|
+
3. Ajouter la commande dans `src/cli.ts`
|
|
222
|
+
4. Ajouter éventuellement un script NPM dans `package.json`
|
|
223
|
+
5. Documenter ici et dans les docs principales
|
|
224
|
+
|
|
225
|
+
## Bonnes pratiques
|
|
226
|
+
|
|
227
|
+
1. **Messages clairs** : Utilisez `chalk` pour des messages colorés et informatifs
|
|
228
|
+
2. **Gestion d'erreurs** : Toujours catch les erreurs et donner un message explicite
|
|
229
|
+
3. **Confirmations** : Pour les opérations destructives, demander confirmation
|
|
230
|
+
4. **Idempotence** : Les scripts doivent pouvoir être exécutés plusieurs fois
|
|
231
|
+
5. **Logs** : Logger les actions importantes pour faciliter le debug
|
|
232
|
+
6. **Exit codes** : Utiliser `process.exit(1)` en cas d'erreur
|
|
233
|
+
|
|
234
|
+
## Tests
|
|
235
|
+
|
|
236
|
+
Pour tester un script :
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
# 1. Compiler le package
|
|
240
|
+
pnpm build
|
|
241
|
+
|
|
242
|
+
# 2. Exécuter la commande
|
|
243
|
+
pnpm lastbrain <commande> [args]
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Contribution
|
|
247
|
+
|
|
248
|
+
Pour contribuer un nouveau script :
|
|
249
|
+
|
|
250
|
+
1. Fork le projet
|
|
251
|
+
2. Créer une branche feature
|
|
252
|
+
3. Implémenter le script avec tests
|
|
253
|
+
4. Documenter dans ce README
|
|
254
|
+
5. Créer une Pull Request
|
|
255
|
+
|
|
256
|
+
## Support
|
|
257
|
+
|
|
258
|
+
Pour toute question ou problème :
|
|
259
|
+
|
|
260
|
+
- Ouvrir une issue sur GitHub
|
|
261
|
+
- Consulter la documentation dans `/docs`
|
|
262
|
+
- Rejoindre le Discord LastBrain
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { spawn, spawnSync, execSync, execFileSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
// Utiliser PROJECT_ROOT si défini (pour pnpm --filter), sinon process.cwd()
|
|
7
|
+
const projectRoot = process.env.PROJECT_ROOT || process.cwd();
|
|
8
|
+
const supabaseDir = path.join(projectRoot, "supabase");
|
|
9
|
+
|
|
10
|
+
const envTargets = [path.join(projectRoot, ".env.local")];
|
|
11
|
+
|
|
12
|
+
// Trouver le template dans le package @lastbrain/app
|
|
13
|
+
const packageAppDir = path
|
|
14
|
+
.dirname(fileURLToPath(import.meta.url))
|
|
15
|
+
.replace(/dist\/scripts$/, "src");
|
|
16
|
+
const envExampleTemplate = path.join(
|
|
17
|
+
packageAppDir,
|
|
18
|
+
"templates/env.example/.env.example"
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
function ensureDirectory(dir: string) {
|
|
22
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function runSupabase(...args: string[]): Buffer | null;
|
|
26
|
+
function runSupabase(captureOutput: boolean, ...args: string[]): Buffer | null;
|
|
27
|
+
function runSupabase(...args: any[]): Buffer | null {
|
|
28
|
+
const bin = path.join(projectRoot, "node_modules", ".bin", "supabase");
|
|
29
|
+
|
|
30
|
+
// Ensure PATH includes common binary locations
|
|
31
|
+
const PATH = process.env.PATH || "";
|
|
32
|
+
const enhancedPath = `/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:${PATH}`;
|
|
33
|
+
|
|
34
|
+
// Check if first argument is a boolean (captureOutput flag)
|
|
35
|
+
const captureOutput = typeof args[0] === "boolean" ? args.shift() : false;
|
|
36
|
+
const stdio = captureOutput ? "pipe" : "inherit";
|
|
37
|
+
|
|
38
|
+
const runners = [
|
|
39
|
+
() =>
|
|
40
|
+
spawnSync("supabase", args, {
|
|
41
|
+
cwd: projectRoot,
|
|
42
|
+
stdio: stdio as any,
|
|
43
|
+
env: { ...process.env, PATH: enhancedPath },
|
|
44
|
+
shell: true,
|
|
45
|
+
}),
|
|
46
|
+
() =>
|
|
47
|
+
spawnSync(bin, args, {
|
|
48
|
+
cwd: projectRoot,
|
|
49
|
+
stdio: stdio as any,
|
|
50
|
+
env: { ...process.env, PATH: enhancedPath },
|
|
51
|
+
}),
|
|
52
|
+
() =>
|
|
53
|
+
spawnSync("pnpm", ["exec", "supabase", ...args], {
|
|
54
|
+
cwd: projectRoot,
|
|
55
|
+
stdio: stdio as any,
|
|
56
|
+
env: { ...process.env, PATH: enhancedPath },
|
|
57
|
+
shell: true,
|
|
58
|
+
}),
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
for (let i = 0; i < runners.length; i++) {
|
|
62
|
+
const result = runners[i]();
|
|
63
|
+
|
|
64
|
+
if (result.error) {
|
|
65
|
+
// Si c'est une erreur ENOENT (commande non trouvée), essayer la suivante
|
|
66
|
+
if ((result.error as any).code === "ENOENT") {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
// Si c'est une autre erreur, la propager
|
|
70
|
+
throw result.error;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Si le processus s'est terminé avec un code de sortie non nul, propager l'erreur
|
|
74
|
+
if (result.status !== 0) {
|
|
75
|
+
const error: any = new Error(
|
|
76
|
+
`Command failed with exit code ${result.status}`
|
|
77
|
+
);
|
|
78
|
+
error.status = result.status;
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Succès !
|
|
83
|
+
return result.stdout || null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
throw new Error(
|
|
87
|
+
"Unable to locate Supabase CLI (install it globally or via pnpm)."
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function ensureSupabaseInit() {
|
|
92
|
+
const configPath = path.join(supabaseDir, "config.toml");
|
|
93
|
+
if (!fs.existsSync(configPath)) {
|
|
94
|
+
runSupabase("init");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function parseEnvFile(filePath: string) {
|
|
99
|
+
const data = fs.readFileSync(filePath, "utf-8");
|
|
100
|
+
return Object.fromEntries(
|
|
101
|
+
data
|
|
102
|
+
.split("\n")
|
|
103
|
+
.map((line) => line.trim())
|
|
104
|
+
.filter((line) => line && !line.startsWith("#"))
|
|
105
|
+
.map((line) => {
|
|
106
|
+
const [key, ...value] = line.split("=");
|
|
107
|
+
return [key, value.join("=")];
|
|
108
|
+
})
|
|
109
|
+
) as Record<string, string>;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function ensureEnvFile(values: Record<string, string>) {
|
|
113
|
+
const content = Object.entries(values)
|
|
114
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
115
|
+
.join("\n");
|
|
116
|
+
|
|
117
|
+
envTargets.forEach((target) => {
|
|
118
|
+
fs.writeFileSync(target, content);
|
|
119
|
+
console.log(`📝 Wrote env to ${target}`);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function buildEnvPayload(creds: Record<string, string | undefined>) {
|
|
124
|
+
return {
|
|
125
|
+
NEXT_PUBLIC_SUPABASE_URL: creds.url ?? "",
|
|
126
|
+
NEXT_PUBLIC_SUPABASE_ANON_KEY: creds.anon ?? "",
|
|
127
|
+
SUPABASE_URL: creds.url ?? "",
|
|
128
|
+
SUPABASE_ANON_KEY: creds.anon ?? "",
|
|
129
|
+
SUPABASE_SERVICE_ROLE_KEY: creds.service ?? "",
|
|
130
|
+
SUPABASE_DB_URL: creds.db ?? "",
|
|
131
|
+
SUPABASE_JWT_SECRET: creds.jwt ?? "",
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function applyEnvToProcess(values: Record<string, string>) {
|
|
136
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL = values.NEXT_PUBLIC_SUPABASE_URL;
|
|
137
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY =
|
|
138
|
+
values.NEXT_PUBLIC_SUPABASE_ANON_KEY;
|
|
139
|
+
process.env.SUPABASE_URL = values.SUPABASE_URL;
|
|
140
|
+
process.env.SUPABASE_ANON_KEY = values.SUPABASE_ANON_KEY;
|
|
141
|
+
process.env.SUPABASE_SERVICE_ROLE_KEY = values.SUPABASE_SERVICE_ROLE_KEY;
|
|
142
|
+
process.env.SUPABASE_DB_URL = values.SUPABASE_DB_URL;
|
|
143
|
+
process.env.SUPABASE_JWT_SECRET = values.SUPABASE_JWT_SECRET;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function copyEnvExample() {
|
|
147
|
+
if (!fs.existsSync(envExampleTemplate)) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
const target = envTargets[0];
|
|
151
|
+
ensureDirectory(path.dirname(target));
|
|
152
|
+
fs.copyFileSync(envExampleTemplate, target);
|
|
153
|
+
return target;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function readSupabaseEnv() {
|
|
157
|
+
const candidate = path.join(supabaseDir, ".env");
|
|
158
|
+
if (fs.existsSync(candidate)) {
|
|
159
|
+
return parseEnvFile(candidate);
|
|
160
|
+
}
|
|
161
|
+
const candidateLocal = path.join(supabaseDir, ".env.local");
|
|
162
|
+
if (fs.existsSync(candidateLocal)) {
|
|
163
|
+
return parseEnvFile(candidateLocal);
|
|
164
|
+
}
|
|
165
|
+
return {} as Record<string, string>;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function getSupabaseStatusEnv() {
|
|
169
|
+
try {
|
|
170
|
+
const result = runSupabase(true, "status", "-o", "env");
|
|
171
|
+
if (!result) return null;
|
|
172
|
+
|
|
173
|
+
const envString = result.toString();
|
|
174
|
+
const lines = envString
|
|
175
|
+
.split("\n")
|
|
176
|
+
.map((line) => line.trim())
|
|
177
|
+
.filter((line) => line && !line.startsWith("#") && line.includes("="));
|
|
178
|
+
|
|
179
|
+
const env: Record<string, string> = {};
|
|
180
|
+
for (const line of lines) {
|
|
181
|
+
const equalIndex = line.indexOf("=");
|
|
182
|
+
if (equalIndex > 0) {
|
|
183
|
+
const key = line.substring(0, equalIndex);
|
|
184
|
+
const value = line
|
|
185
|
+
.substring(equalIndex + 1)
|
|
186
|
+
.replace(/^["']|["']$/g, "");
|
|
187
|
+
env[key] = value;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return Object.keys(env).length > 0 ? env : null;
|
|
192
|
+
} catch {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function buildCredsFromStatus(status: Record<string, any>) {
|
|
198
|
+
if (!status) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
url: status.API_URL ?? status.SUPABASE_URL ?? "",
|
|
203
|
+
anon: status.ANON_KEY ?? "",
|
|
204
|
+
service: status.SERVICE_ROLE_KEY ?? status.SECRET_KEY ?? "",
|
|
205
|
+
jwt: status.JWT_SECRET ?? "",
|
|
206
|
+
db: status.DB_URL ?? status.DATABASE_URL ?? "",
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function findLocalKeysFallback() {
|
|
211
|
+
const env = readSupabaseEnv();
|
|
212
|
+
if (Object.keys(env).length === 0) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
url: env.SUPABASE_URL ?? env.API_URL ?? null,
|
|
218
|
+
anon: env.SUPABASE_ANON_KEY ?? env.ANON_KEY ?? null,
|
|
219
|
+
service: env.SUPABASE_SERVICE_ROLE_KEY ?? env.SERVICE_ROLE_KEY ?? null,
|
|
220
|
+
jwt: env.JWT_SECRET ?? null,
|
|
221
|
+
db: env.SUPABASE_DB_URL ?? env.DB_URL ?? null,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function wait(ms: number) {
|
|
226
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async function waitForStatus() {
|
|
230
|
+
const maxAttempts = 10;
|
|
231
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
232
|
+
const status = getSupabaseStatusEnv();
|
|
233
|
+
if (status && Object.keys(status).length > 0) {
|
|
234
|
+
return status;
|
|
235
|
+
}
|
|
236
|
+
await wait(1000);
|
|
237
|
+
}
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function gatherCredentials() {
|
|
242
|
+
const envStatus = await waitForStatus();
|
|
243
|
+
let creds = envStatus ? buildCredsFromStatus(envStatus) : null;
|
|
244
|
+
|
|
245
|
+
if (!creds || !creds.url || !creds.anon) {
|
|
246
|
+
const fallback = findLocalKeysFallback();
|
|
247
|
+
if (fallback) {
|
|
248
|
+
creds = {
|
|
249
|
+
url: fallback.url ?? "",
|
|
250
|
+
anon: fallback.anon ?? "",
|
|
251
|
+
service: fallback.service ?? "",
|
|
252
|
+
jwt: fallback.jwt ?? "",
|
|
253
|
+
db: fallback.db ?? "",
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return creds;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async function main() {
|
|
262
|
+
ensureSupabaseInit();
|
|
263
|
+
|
|
264
|
+
const PATH = process.env.PATH || "";
|
|
265
|
+
const enhancedPath = `/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:${PATH}`;
|
|
266
|
+
|
|
267
|
+
const migrationResult = spawnSync(
|
|
268
|
+
"pnpm",
|
|
269
|
+
["--filter", "web", "db:migrations:sync"],
|
|
270
|
+
{
|
|
271
|
+
cwd: projectRoot,
|
|
272
|
+
stdio: "inherit",
|
|
273
|
+
env: { ...process.env, PATH: enhancedPath },
|
|
274
|
+
shell: true,
|
|
275
|
+
}
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
if (migrationResult.error || migrationResult.status !== 0) {
|
|
279
|
+
console.warn("⚠️ Migration sync failed, continuing anyway...");
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
console.log("⚙️ Starting Supabase...");
|
|
283
|
+
try {
|
|
284
|
+
runSupabase("start");
|
|
285
|
+
} catch (error) {
|
|
286
|
+
console.warn("⚠️ Supabase start had issues, continuing...");
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
console.log("🔄 Resetting database...");
|
|
290
|
+
try {
|
|
291
|
+
runSupabase("db", "reset");
|
|
292
|
+
console.log("✅ Database reset complete.");
|
|
293
|
+
} catch (error) {
|
|
294
|
+
console.error("⚠️ supabase db reset failed:", error);
|
|
295
|
+
throw error;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
console.log("🛠 Waiting for services to restart...");
|
|
299
|
+
await wait(3000);
|
|
300
|
+
|
|
301
|
+
const creds = await gatherCredentials();
|
|
302
|
+
if (creds && creds.url && creds.anon) {
|
|
303
|
+
const payload = buildEnvPayload(creds);
|
|
304
|
+
ensureEnvFile(payload);
|
|
305
|
+
applyEnvToProcess(payload);
|
|
306
|
+
console.log("✅ Environment configured successfully!");
|
|
307
|
+
} else {
|
|
308
|
+
const example = copyEnvExample();
|
|
309
|
+
if (example) {
|
|
310
|
+
console.log("ℹ️ Copied .env.example into .env.local as fallback.");
|
|
311
|
+
}
|
|
312
|
+
console.warn(
|
|
313
|
+
"⚠️ Impossible de récupérer automatiquement les accès Supabase."
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (process.argv.includes("--with-dev")) {
|
|
318
|
+
console.log("🚀 Launching Next dev server...");
|
|
319
|
+
const devProcess = spawn("pnpm", ["--filter", "web", "dev"], {
|
|
320
|
+
cwd: projectRoot,
|
|
321
|
+
stdio: "inherit",
|
|
322
|
+
});
|
|
323
|
+
devProcess.on("exit", (code) => {
|
|
324
|
+
console.log("🧹 Stopping Supabase");
|
|
325
|
+
try {
|
|
326
|
+
runSupabase("stop");
|
|
327
|
+
} catch {
|
|
328
|
+
// Ignore errors when stopping
|
|
329
|
+
}
|
|
330
|
+
process.exit(code ?? 0);
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
main().catch((error) => {
|
|
336
|
+
console.error(error);
|
|
337
|
+
process.exit(1);
|
|
338
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
import { getModuleConfigs } from "../modules/module-loader.js";
|
|
6
|
+
import type { ModuleBuildConfig } from "@lastbrain/core";
|
|
7
|
+
|
|
8
|
+
// Utiliser PROJECT_ROOT si défini (pour pnpm --filter), sinon process.cwd()
|
|
9
|
+
const projectRoot = process.env.PROJECT_ROOT || process.cwd();
|
|
10
|
+
const migrationsDir = path.join(projectRoot, "supabase/migrations");
|
|
11
|
+
|
|
12
|
+
function ensureDirectory(dir: string) {
|
|
13
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
enum CopyStrategy {
|
|
17
|
+
overwrite,
|
|
18
|
+
skip,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function getModulePackageDir(moduleName: string) {
|
|
22
|
+
return path.join(projectRoot, "node_modules", ...moduleName.split("/"));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface ModuleMigrationEntry {
|
|
26
|
+
enabled: boolean;
|
|
27
|
+
priority?: number;
|
|
28
|
+
path?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function copyMigration(
|
|
32
|
+
moduleConfig: ModuleBuildConfig,
|
|
33
|
+
migration: ModuleMigrationEntry,
|
|
34
|
+
file: string,
|
|
35
|
+
index: number
|
|
36
|
+
) {
|
|
37
|
+
const moduleDir = getModulePackageDir(moduleConfig.moduleName);
|
|
38
|
+
const migrationsPath = migration.path ?? "supabase/migrations";
|
|
39
|
+
const sourceDir = path.join(moduleDir, migrationsPath);
|
|
40
|
+
const sourceFile = path.join(sourceDir, file);
|
|
41
|
+
|
|
42
|
+
if (!fs.existsSync(sourceFile)) {
|
|
43
|
+
console.warn(`⚠️ Missing migration file ${sourceFile}`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
ensureDirectory(migrationsDir);
|
|
48
|
+
const moduleSlug = moduleConfig.moduleName.replace("@lastbrain/", "module-");
|
|
49
|
+
const prefix = String((migration.priority ?? 0) * 10 + index + 1).padStart(
|
|
50
|
+
3,
|
|
51
|
+
"0"
|
|
52
|
+
);
|
|
53
|
+
const fileName = `${prefix}_${moduleSlug}_${file}`;
|
|
54
|
+
const targetFile = path.join(migrationsDir, fileName);
|
|
55
|
+
|
|
56
|
+
fs.copyFileSync(sourceFile, targetFile);
|
|
57
|
+
console.log(`📜 Copied migration ${fileName}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function syncModuleMigrations() {
|
|
61
|
+
const moduleConfigs = getModuleConfigs();
|
|
62
|
+
moduleConfigs.forEach((moduleConfig) => {
|
|
63
|
+
const migrationBlock = moduleConfig.migrations;
|
|
64
|
+
if (!migrationBlock || !migrationBlock.enabled) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const moduleMigrationPath = path.join(
|
|
69
|
+
getModulePackageDir(moduleConfig.moduleName),
|
|
70
|
+
migrationBlock.path ?? "supabase/migrations"
|
|
71
|
+
);
|
|
72
|
+
let files = migrationBlock.files;
|
|
73
|
+
if (!files?.length && fs.existsSync(moduleMigrationPath)) {
|
|
74
|
+
files = fs
|
|
75
|
+
.readdirSync(moduleMigrationPath)
|
|
76
|
+
.filter((file) => file.endsWith(".sql"))
|
|
77
|
+
.sort();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
files?.forEach((file, index) => {
|
|
81
|
+
copyMigration(moduleConfig, migrationBlock, file, index);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
syncModuleMigrations();
|