@lastbrain/app 0.1.0 → 0.1.1

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.
@@ -1,338 +0,0 @@
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
- });
@@ -1,86 +0,0 @@
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();
@@ -1,218 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
-
5
- const GENERATED_HEADER = "// GENERATED BY LASTBRAIN APP-SHELL\n";
6
-
7
- const scriptDir = path.dirname(fileURLToPath(import.meta.url));
8
- const packageRoot = path.join(scriptDir, "..", "..");
9
- const projectRoot = path.join(packageRoot, "..", "..");
10
- const srcAppShell = path.join(projectRoot, "packages/app/src/app-shell");
11
- const destAppShell = path.join(projectRoot, "apps/web/app");
12
- const webPackage = path.join(projectRoot, "apps/web/package.json");
13
-
14
- const scriptsToEnsure = {
15
- dev: "next dev",
16
- build: "next build",
17
- "build:modules": "pnpm --filter @lastbrain/app module:build",
18
- "db:migrations:sync": "pnpm --filter @lastbrain/app db:migrations:sync",
19
- "db:init": "pnpm --filter @lastbrain/app db:init",
20
- "readme:create": "pnpm --filter @lastbrain/app readme:create"
21
- };
22
-
23
- const dependenciesToEnsure = {
24
- "@lastbrain/app": "workspace:*",
25
- "@lastbrain/core": "workspace:*",
26
- "@lastbrain/ui": "workspace:*"
27
- };
28
-
29
- const gitignoreTemplate = path.join(projectRoot, "packages/app/src/templates/gitignore/.gitignore");
30
- const consumerGitignore = path.join(projectRoot, "apps/web/.gitignore");
31
- const envTemplate = path.join(projectRoot, "packages/app/src/templates/env.example/.env.example");
32
- const consumerEnvExample = path.join(projectRoot, "apps/web/.env.example");
33
- const consumerEnvLocal = path.join(projectRoot, "apps/web/.env.local");
34
-
35
- function ensureDirectory(dir: string) {
36
- fs.mkdirSync(dir, { recursive: true });
37
- }
38
-
39
- function normalizeContent(data: string) {
40
- const trimmed = data.startsWith(GENERATED_HEADER) ? data.slice(GENERATED_HEADER.length) : data;
41
- const sanitized = trimmed.trimStart();
42
- return `${GENERATED_HEADER}\n${sanitized}`;
43
- }
44
-
45
- function syncFile(src: string, dest: string) {
46
- const relativePath = path.relative(destAppShell, dest);
47
- let content: string;
48
-
49
- if (relativePath === "layout.tsx") {
50
- content = `${GENERATED_HEADER}\nimport { RootLayout } from "@lastbrain/app";\n\nexport default RootLayout;\n`;
51
- } else {
52
- const data = fs.readFileSync(src, "utf-8");
53
- content = normalizeContent(data);
54
- }
55
-
56
- ensureDirectory(path.dirname(dest));
57
- fs.writeFileSync(dest, content);
58
- return dest;
59
- }
60
-
61
- function copyDirectory(srcDir: string, destDir: string) {
62
- const stats = fs.statSync(srcDir);
63
- if (stats.isDirectory()) {
64
- ensureDirectory(destDir);
65
- const entries = fs.readdirSync(srcDir, { withFileTypes: true });
66
- for (const entry of entries) {
67
- copyDirectory(path.join(srcDir, entry.name), path.join(destDir, entry.name));
68
- }
69
- } else if (stats.isFile()) {
70
- syncFile(srcDir, destDir);
71
- }
72
- }
73
-
74
- function syncAppShell() {
75
- if (!fs.existsSync(srcAppShell)) {
76
- throw new Error(`Source app-shell not found at ${srcAppShell}`);
77
- }
78
-
79
- const copied: string[] = [];
80
- const recurse = (src: string, dest: string) => {
81
- const stats = fs.statSync(src);
82
- if (stats.isDirectory()) {
83
- ensureDirectory(dest);
84
- const entries = fs.readdirSync(src, { withFileTypes: true });
85
- for (const entry of entries) {
86
- recurse(path.join(src, entry.name), path.join(dest, entry.name));
87
- }
88
- } else {
89
- syncFile(src, dest);
90
- copied.push(dest);
91
- }
92
- };
93
-
94
- recurse(srcAppShell, destAppShell);
95
- return copied;
96
- }
97
-
98
- function cleanupStaleGroupFiles() {
99
- const staleFiles = [
100
- path.join(destAppShell, "(auth)", "page.tsx"),
101
- path.join(destAppShell, "(admin)", "page.tsx")
102
- ];
103
-
104
- staleFiles.forEach((file) => {
105
- if (fs.existsSync(file)) {
106
- fs.unlinkSync(file);
107
- }
108
- });
109
-
110
- const staleDirs = [
111
- path.join(destAppShell, "(auth)", "dashboard"),
112
- path.join(destAppShell, "(admin)", "dashboard")
113
- ];
114
-
115
- staleDirs.forEach((dir) => {
116
- if (fs.existsSync(dir)) {
117
- fs.rmSync(dir, { recursive: true, force: true });
118
- }
119
- });
120
- }
121
-
122
- function mergeScripts(base: Record<string, string>, additions: Record<string, string>) {
123
- return { ...base, ...additions };
124
- }
125
-
126
- function syncPackageJson() {
127
- ensureDirectory(path.dirname(webPackage));
128
- const defaultPkg = {
129
- name: "web",
130
- private: true,
131
- version: "0.1.0",
132
- type: "module"
133
- };
134
-
135
- const pkg = fs.existsSync(webPackage) ? JSON.parse(fs.readFileSync(webPackage, "utf-8")) : defaultPkg;
136
- pkg.scripts = mergeScripts(pkg.scripts ?? {}, scriptsToEnsure);
137
- pkg.dependencies = { ...(pkg.dependencies ?? {}), ...dependenciesToEnsure };
138
-
139
- fs.writeFileSync(webPackage, JSON.stringify(pkg, null, 2) + "\n");
140
- return { scripts: Object.keys(scriptsToEnsure), dependencies: Object.keys(dependenciesToEnsure) };
141
- }
142
-
143
- function syncEnvExample() {
144
- if (!fs.existsSync(envTemplate)) {
145
- return null;
146
- }
147
-
148
- const shouldOverwrite =
149
- !fs.existsSync(consumerEnvExample) ||
150
- fs.readFileSync(consumerEnvExample, "utf-8").includes("GENERATED BY LASTBRAIN");
151
-
152
- if (!shouldOverwrite) {
153
- return null;
154
- }
155
-
156
- fs.copyFileSync(envTemplate, consumerEnvExample);
157
- return consumerEnvExample;
158
- }
159
-
160
- function ensureEnvLocal() {
161
- if (fs.existsSync(consumerEnvLocal)) {
162
- return null;
163
- }
164
-
165
- if (!fs.existsSync(envTemplate)) {
166
- return null;
167
- }
168
-
169
- fs.copyFileSync(envTemplate, consumerEnvLocal);
170
- return consumerEnvLocal;
171
- }
172
-
173
- function syncGitignore() {
174
- if (!fs.existsSync(gitignoreTemplate)) {
175
- return null;
176
- }
177
-
178
- const shouldOverwrite =
179
- !fs.existsSync(consumerGitignore) ||
180
- fs.readFileSync(consumerGitignore, "utf-8").includes("GENERATED BY LASTBRAIN");
181
-
182
- if (!shouldOverwrite) {
183
- return null;
184
- }
185
-
186
- fs.copyFileSync(gitignoreTemplate, consumerGitignore);
187
- return consumerGitignore;
188
- }
189
-
190
- function main() {
191
- ensureDirectory(destAppShell);
192
- const copiedFiles = syncAppShell();
193
- cleanupStaleGroupFiles();
194
- const changes = syncPackageJson();
195
-
196
- const gitignoreSynced = syncGitignore();
197
- const envExampleSynced = syncEnvExample();
198
- const envLocalCreated = ensureEnvLocal();
199
-
200
- console.log("✅ apps/web synced with @lastbrain/app");
201
- console.log(`Copied or updated files (${copiedFiles.length}):`);
202
- copiedFiles.forEach((file) => console.log(` • ${path.relative(projectRoot, file)}`));
203
- console.log("Scripts ensured in apps/web/package.json:");
204
- changes.scripts.forEach((script) => console.log(` • ${script}`));
205
- console.log("Dependencies ensured in apps/web/package.json:");
206
- changes.dependencies.forEach((dep) => console.log(` • ${dep}`));
207
- if (gitignoreSynced) {
208
- console.log(`.gitignore ensured at ${path.relative(projectRoot, gitignoreSynced)}`);
209
- }
210
- if (envExampleSynced) {
211
- console.log(`.env.example ensured at ${path.relative(projectRoot, envExampleSynced)}`);
212
- }
213
- if (envLocalCreated) {
214
- console.log(`.env.local ensured at ${path.relative(projectRoot, envLocalCreated)}`);
215
- }
216
- }
217
-
218
- main();