@lastbrain/app 0.1.22 → 0.1.24

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.
@@ -75,17 +75,20 @@ export async function initApp(options: InitAppOptions) {
75
75
  // 4. Créer les fichiers de configuration
76
76
  await createConfigFiles(targetDir, force, useHeroUI);
77
77
 
78
- // 5. Créer .gitignore et .env.local.example
78
+ // 5. Créer le système de proxy storage
79
+ await createStorageProxy(targetDir, force);
80
+
81
+ // 6. Créer .gitignore et .env.local.example
79
82
  await createGitIgnore(targetDir, force);
80
83
  await createEnvExample(targetDir, force);
81
84
 
82
- // 6. Créer la structure Supabase avec migrations
85
+ // 7. Créer la structure Supabase avec migrations
83
86
  await createSupabaseStructure(targetDir, force);
84
87
 
85
- // 7. Ajouter les scripts NPM
88
+ // 8. Ajouter les scripts NPM
86
89
  await addScriptsToPackageJson(targetDir);
87
90
 
88
- // 8. Enregistrer les modules sélectionnés
91
+ // 9. Enregistrer les modules sélectionnés
89
92
  if (withAuth || selectedModules.length > 0) {
90
93
  await saveModulesConfig(targetDir, selectedModules, withAuth);
91
94
  }
@@ -192,6 +195,62 @@ async function ensurePackageJson(targetDir: string, projectName: string) {
192
195
  }
193
196
  }
194
197
 
198
+ /**
199
+ * Récupère les versions actuelles des packages LastBrain depuis npm ou le monorepo
200
+ */
201
+ function getLastBrainVersions(targetDir: string): {
202
+ app: string;
203
+ core: string;
204
+ ui: string;
205
+ moduleAuth: string;
206
+ } {
207
+ const targetIsInMonorepo = fs.existsSync(path.join(targetDir, "../../../packages/core/package.json")) ||
208
+ fs.existsSync(path.join(targetDir, "../../packages/core/package.json"));
209
+
210
+ if (targetIsInMonorepo) {
211
+ // Dans le monorepo, utiliser workspace:*
212
+ return {
213
+ app: "workspace:*",
214
+ core: "workspace:*",
215
+ ui: "workspace:*",
216
+ moduleAuth: "workspace:*",
217
+ };
218
+ }
219
+
220
+ // Hors monorepo, lire les versions depuis les package.json locaux (plus fiable que "latest")
221
+ try {
222
+ // Essayer de lire depuis node_modules/@lastbrain (si init-app a été installé)
223
+ const appPkgPath = path.join(__dirname, "../../package.json");
224
+
225
+ if (fs.existsSync(appPkgPath)) {
226
+ const appPkg = JSON.parse(fs.readFileSync(appPkgPath, "utf-8"));
227
+ const appVersion = `^${appPkg.version}`;
228
+
229
+ // Lire les versions des dépendances de @lastbrain/app
230
+ const coreDep = appPkg.dependencies?.["@lastbrain/core"];
231
+ const uiDep = appPkg.dependencies?.["@lastbrain/ui"];
232
+ const authDep = appPkg.dependencies?.["@lastbrain/module-auth"];
233
+
234
+ return {
235
+ app: appVersion,
236
+ core: coreDep || appVersion,
237
+ ui: uiDep || appVersion,
238
+ moduleAuth: authDep || appVersion,
239
+ };
240
+ }
241
+ } catch (error) {
242
+ console.warn(chalk.yellow("⚠️ Impossible de lire les versions locales, utilisation de 'latest'"));
243
+ }
244
+
245
+ // Fallback: utiliser "latest"
246
+ return {
247
+ app: "latest",
248
+ core: "latest",
249
+ ui: "latest",
250
+ moduleAuth: "latest",
251
+ };
252
+ }
253
+
195
254
  async function addDependencies(
196
255
  targetDir: string,
197
256
  useHeroUI: boolean,
@@ -203,33 +262,30 @@ async function addDependencies(
203
262
 
204
263
  console.log(chalk.yellow("\n📦 Configuration des dépendances..."));
205
264
 
206
- // Détecter si on est dans le monorepo (développement local)
207
- // Vérifier si le projet cible est à l'intérieur du monorepo
208
- const targetIsInMonorepo = fs.existsSync(path.join(targetDir, "../../../packages/core/package.json")) ||
209
- fs.existsSync(path.join(targetDir, "../../packages/core/package.json"));
210
- const latestVersion = targetIsInMonorepo ? "workspace:*" : "latest";
265
+ const versions = getLastBrainVersions(targetDir);
211
266
 
212
267
  // Dependencies
213
268
  const requiredDeps: Record<string, string> = {
214
269
  next: "^15.0.3",
215
270
  react: "^18.3.1",
216
271
  "react-dom": "^18.3.1",
217
- "@lastbrain/app": latestVersion,
218
- "@lastbrain/core": latestVersion,
219
- "@lastbrain/ui": latestVersion,
272
+ "@lastbrain/app": versions.app,
273
+ "@lastbrain/core": versions.core,
274
+ "@lastbrain/ui": versions.ui,
220
275
  "next-themes": "^0.4.6",
221
276
  };
222
277
 
223
278
  // Ajouter module-auth si demandé
224
279
  if (withAuth) {
225
- requiredDeps["@lastbrain/module-auth"] = latestVersion;
280
+ requiredDeps["@lastbrain/module-auth"] = versions.moduleAuth;
226
281
  }
227
282
 
228
283
  // Ajouter les autres modules sélectionnés
229
284
  for (const moduleName of selectedModules) {
230
285
  const moduleInfo = AVAILABLE_MODULES.find(m => m.name === moduleName);
231
286
  if (moduleInfo && moduleInfo.package !== "@lastbrain/module-auth") {
232
- requiredDeps[moduleInfo.package] = latestVersion;
287
+ // Pour les autres modules, utiliser "latest" ou la version depuis le package
288
+ requiredDeps[moduleInfo.package] = versions.app; // Utiliser la même version que app
233
289
  }
234
290
  }
235
291
 
@@ -270,6 +326,7 @@ async function addDependencies(
270
326
  requiredDeps["@heroui/switch"] = "^2.2.24";
271
327
  requiredDeps["@heroui/table"] = "^2.2.27";
272
328
  requiredDeps["@heroui/tabs"] = "^2.2.24";
329
+ requiredDeps["@heroui/system"] = "^2.4.23"; // Ajout pour HeroUIProvider
273
330
  requiredDeps["@heroui/toast"] = "^2.0.17";
274
331
  requiredDeps["@heroui/tooltip"] = "^2.2.24";
275
332
  requiredDeps["@heroui/user"] = "^2.2.22";
@@ -1009,21 +1066,11 @@ async function addScriptsToPackageJson(targetDir: string) {
1009
1066
  build: "next build",
1010
1067
  start: "next start",
1011
1068
  lint: "next lint",
1012
- lastbrain: targetIsInMonorepo
1013
- ? "pnpm exec lastbrain"
1014
- : "node node_modules/@lastbrain/app/dist/cli.js",
1015
- "build:modules": targetIsInMonorepo
1016
- ? "pnpm exec lastbrain module:build"
1017
- : "node node_modules/@lastbrain/app/dist/scripts/module-build.js",
1018
- "db:migrations:sync": targetIsInMonorepo
1019
- ? "pnpm exec lastbrain db:migrations:sync"
1020
- : "node node_modules/@lastbrain/app/dist/scripts/db-migrations-sync.js",
1021
- "db:init": targetIsInMonorepo
1022
- ? "pnpm exec lastbrain db:init"
1023
- : "node node_modules/@lastbrain/app/dist/scripts/db-init.js",
1024
- "readme:create": targetIsInMonorepo
1025
- ? "pnpm exec lastbrain readme:create"
1026
- : "node node_modules/@lastbrain/app/dist/scripts/readme-build.js",
1069
+ lastbrain: "node node_modules/@lastbrain/app/dist/cli.js",
1070
+ "build:modules": "node node_modules/@lastbrain/app/dist/scripts/module-build.js",
1071
+ "db:migrations:sync": "node node_modules/@lastbrain/app/dist/scripts/db-migrations-sync.js",
1072
+ "db:init": "node node_modules/@lastbrain/app/dist/scripts/db-init.js",
1073
+ "readme:create": "node node_modules/@lastbrain/app/dist/scripts/readme-build.js",
1027
1074
  };
1028
1075
 
1029
1076
  pkg.scripts = { ...pkg.scripts, ...scripts };
@@ -1075,3 +1122,349 @@ async function saveModulesConfig(
1075
1122
  await fs.writeJson(modulesConfigPath, { modules }, { spaces: 2 });
1076
1123
  console.log(chalk.green("✓ Configuration des modules sauvegardée"));
1077
1124
  }
1125
+
1126
+ async function createStorageProxy(targetDir: string, force: boolean) {
1127
+ console.log(chalk.yellow("\n🗂️ Création du système de proxy storage..."));
1128
+
1129
+ // Créer le dossier lib
1130
+ const libDir = path.join(targetDir, "lib");
1131
+ await fs.ensureDir(libDir);
1132
+
1133
+ // 1. Créer lib/bucket-config.ts
1134
+ const bucketConfigPath = path.join(libDir, "bucket-config.ts");
1135
+ if (!fs.existsSync(bucketConfigPath) || force) {
1136
+ const bucketConfigContent = `/**
1137
+ * Storage configuration for buckets and access control
1138
+ */
1139
+
1140
+ export interface BucketConfig {
1141
+ name: string;
1142
+ isPublic: boolean;
1143
+ description: string;
1144
+ allowedFileTypes?: string[];
1145
+ maxFileSize?: number; // in bytes
1146
+ customAccessControl?: (userId: string, filePath: string) => boolean;
1147
+ }
1148
+
1149
+ export const BUCKET_CONFIGS: Record<string, BucketConfig> = {
1150
+ avatar: {
1151
+ name: "avatar",
1152
+ isPublic: true,
1153
+ description: "User profile pictures and avatars",
1154
+ allowedFileTypes: ["image/jpeg", "image/png", "image/webp", "image/gif"],
1155
+ maxFileSize: 10 * 1024 * 1024, // 10MB
1156
+ },
1157
+ app: {
1158
+ name: "app",
1159
+ isPublic: false,
1160
+ description: "Private user files and documents",
1161
+ maxFileSize: 100 * 1024 * 1024, // 100MB
1162
+ customAccessControl: (userId: string, filePath: string) => {
1163
+ // Users can only access files in their own folder (app/{userId}/...)
1164
+ return filePath.startsWith(\`\${userId}/\`);
1165
+ },
1166
+ },
1167
+ // Example for future buckets:
1168
+ // public: {
1169
+ // name: "public",
1170
+ // isPublic: true,
1171
+ // description: "Publicly accessible files like logos, banners",
1172
+ // allowedFileTypes: ["image/jpeg", "image/png", "image/webp", "application/pdf"],
1173
+ // maxFileSize: 50 * 1024 * 1024, // 50MB
1174
+ // },
1175
+ // documents: {
1176
+ // name: "documents",
1177
+ // isPublic: false,
1178
+ // description: "Private documents requiring authentication",
1179
+ // allowedFileTypes: ["application/pdf", "application/msword", "text/plain"],
1180
+ // maxFileSize: 25 * 1024 * 1024, // 25MB
1181
+ // },
1182
+ };
1183
+
1184
+ /**
1185
+ * Get bucket configuration
1186
+ */
1187
+ export function getBucketConfig(bucketName: string): BucketConfig | null {
1188
+ return BUCKET_CONFIGS[bucketName] || null;
1189
+ }
1190
+
1191
+ /**
1192
+ * Check if bucket is public
1193
+ */
1194
+ export function isPublicBucket(bucketName: string): boolean {
1195
+ const config = getBucketConfig(bucketName);
1196
+ return config?.isPublic ?? false;
1197
+ }
1198
+
1199
+ /**
1200
+ * Check if user has access to a specific file
1201
+ */
1202
+ export function hasFileAccess(bucketName: string, userId: string, filePath: string): boolean {
1203
+ const config = getBucketConfig(bucketName);
1204
+ if (!config) return false;
1205
+
1206
+ // Public buckets are accessible to everyone
1207
+ if (config.isPublic) return true;
1208
+
1209
+ // Private buckets require authentication
1210
+ if (!userId) return false;
1211
+
1212
+ // Apply custom access control if defined
1213
+ if (config.customAccessControl) {
1214
+ return config.customAccessControl(userId, filePath);
1215
+ }
1216
+
1217
+ return true;
1218
+ }
1219
+
1220
+ /**
1221
+ * Validate file type for bucket
1222
+ */
1223
+ export function isValidFileType(bucketName: string, contentType: string): boolean {
1224
+ const config = getBucketConfig(bucketName);
1225
+ if (!config || !config.allowedFileTypes) return true;
1226
+
1227
+ return config.allowedFileTypes.includes(contentType);
1228
+ }
1229
+
1230
+ /**
1231
+ * Check if file size is within bucket limits
1232
+ */
1233
+ export function isValidFileSize(bucketName: string, fileSize: number): boolean {
1234
+ const config = getBucketConfig(bucketName);
1235
+ if (!config || !config.maxFileSize) return true;
1236
+
1237
+ return fileSize <= config.maxFileSize;
1238
+ }`;
1239
+
1240
+ await fs.writeFile(bucketConfigPath, bucketConfigContent);
1241
+ console.log(chalk.green("✓ lib/bucket-config.ts créé"));
1242
+ }
1243
+
1244
+ // 2. Créer lib/storage.ts
1245
+ const storagePath = path.join(libDir, "storage.ts");
1246
+ if (!fs.existsSync(storagePath) || force) {
1247
+ const storageContent = `/**
1248
+ * Build storage proxy URL for files in Supabase buckets
1249
+ *
1250
+ * @param bucket - The bucket name (e.g., "avatar", "app")
1251
+ * @param path - The file path within the bucket
1252
+ * @returns Proxied URL (e.g., "/api/storage/avatar/user_128_123456.webp")
1253
+ */
1254
+ export function buildStorageUrl(bucket: string, path: string): string {
1255
+ // Remove leading slash if present
1256
+ const cleanPath = path.startsWith("/") ? path.slice(1) : path;
1257
+
1258
+ // Remove bucket prefix from path if present (e.g., "avatar/file.jpg" -> "file.jpg")
1259
+ const pathWithoutBucket = cleanPath.startsWith(bucket + "/")
1260
+ ? cleanPath.slice(bucket.length + 1)
1261
+ : cleanPath;
1262
+
1263
+ return \`/api/storage/\${bucket}/\${pathWithoutBucket}\`;
1264
+ }
1265
+
1266
+ /**
1267
+ * Extract bucket and path from a storage URL
1268
+ *
1269
+ * @param url - Storage URL (can be proxied URL or Supabase public URL)
1270
+ * @returns Object with bucket and path, or null if not a valid storage URL
1271
+ */
1272
+ export function parseStorageUrl(url: string): { bucket: string; path: string } | null {
1273
+ // Handle proxy URLs like "/api/storage/avatar/file.jpg"
1274
+ const proxyMatch = url.match(/^\\/api\\/storage\\/([^\\/]+)\\/(.+)$/);
1275
+ if (proxyMatch) {
1276
+ return {
1277
+ bucket: proxyMatch[1],
1278
+ path: proxyMatch[2]
1279
+ };
1280
+ }
1281
+
1282
+ // Handle Supabase public URLs
1283
+ const supabaseMatch = url.match(/\\/storage\\/v1\\/object\\/public\\/([^\\/]+)\\/(.+)$/);
1284
+ if (supabaseMatch) {
1285
+ return {
1286
+ bucket: supabaseMatch[1],
1287
+ path: supabaseMatch[2]
1288
+ };
1289
+ }
1290
+
1291
+ return null;
1292
+ }
1293
+
1294
+ /**
1295
+ * Convert a Supabase storage path to proxy URL
1296
+ *
1297
+ * @param storagePath - Path like "avatar/file.jpg" or "app/user/file.pdf"
1298
+ * @returns Proxied URL
1299
+ */
1300
+ export function storagePathToProxyUrl(storagePath: string): string {
1301
+ const parts = storagePath.split("/");
1302
+ if (parts.length < 2) {
1303
+ throw new Error("Invalid storage path format");
1304
+ }
1305
+
1306
+ const bucket = parts[0];
1307
+ const path = parts.slice(1).join("/");
1308
+
1309
+ return buildStorageUrl(bucket, path);
1310
+ }
1311
+
1312
+ /**
1313
+ * List of public buckets that don't require authentication
1314
+ */
1315
+ export const PUBLIC_BUCKETS = ["avatar"];
1316
+
1317
+ /**
1318
+ * List of private buckets that require authentication
1319
+ */
1320
+ export const PRIVATE_BUCKETS = ["app"];
1321
+
1322
+ /**
1323
+ * Check if a bucket is public
1324
+ */
1325
+ export function isPublicBucket(bucket: string): boolean {
1326
+ return PUBLIC_BUCKETS.includes(bucket);
1327
+ }
1328
+
1329
+ /**
1330
+ * Check if a bucket is private
1331
+ */
1332
+ export function isPrivateBucket(bucket: string): boolean {
1333
+ return PRIVATE_BUCKETS.includes(bucket);
1334
+ }`;
1335
+
1336
+ await fs.writeFile(storagePath, storageContent);
1337
+ console.log(chalk.green("✓ lib/storage.ts créé"));
1338
+ }
1339
+
1340
+ // 3. Créer app/api/storage/[bucket]/[...path]/route.ts
1341
+ const apiStorageDir = path.join(targetDir, "app", "api", "storage", "[bucket]", "[...path]");
1342
+ await fs.ensureDir(apiStorageDir);
1343
+
1344
+ const routePath = path.join(apiStorageDir, "route.ts");
1345
+ if (!fs.existsSync(routePath) || force) {
1346
+ const routeContent = `import { NextRequest, NextResponse } from "next/server";
1347
+ import { getSupabaseServerClient } from "@lastbrain/core/server";
1348
+ import { getBucketConfig, hasFileAccess } from "@/lib/bucket-config";
1349
+
1350
+ /**
1351
+ * GET /api/storage/[bucket]/[...path]
1352
+ * Proxy for Supabase Storage files with clean URLs and access control
1353
+ *
1354
+ * Examples:
1355
+ * - /api/storage/avatar/user_128_123456.webp
1356
+ * - /api/storage/app/user/documents/file.pdf
1357
+ */
1358
+ export async function GET(
1359
+ request: NextRequest,
1360
+ props: { params: Promise<{ bucket: string; path: string[] }> }
1361
+ ) {
1362
+ try {
1363
+ const { bucket, path } = await props.params;
1364
+ const filePath = path.join("/");
1365
+
1366
+ // Check if bucket exists in our configuration
1367
+ const bucketConfig = getBucketConfig(bucket);
1368
+ if (!bucketConfig) {
1369
+ return new NextResponse("Bucket not allowed", { status: 403 });
1370
+ }
1371
+
1372
+ const supabase = await getSupabaseServerClient();
1373
+ let userId: string | null = null;
1374
+
1375
+ // Get user for private buckets or custom access control
1376
+ if (!bucketConfig.isPublic) {
1377
+ const { data: { user }, error: authError } = await supabase.auth.getUser();
1378
+
1379
+ if (authError || !user) {
1380
+ return new NextResponse("Unauthorized", { status: 401 });
1381
+ }
1382
+
1383
+ userId = user.id;
1384
+ }
1385
+
1386
+ // Check file access permissions
1387
+ if (!hasFileAccess(bucket, userId || "", filePath)) {
1388
+ return new NextResponse("Forbidden - Access denied to this file", { status: 403 });
1389
+ }
1390
+
1391
+ // Get file from Supabase Storage
1392
+ const { data: file, error } = await supabase.storage
1393
+ .from(bucket)
1394
+ .download(filePath);
1395
+
1396
+ if (error) {
1397
+ console.error("Storage download error:", error);
1398
+ if (error.message.includes("not found")) {
1399
+ return new NextResponse("File not found", { status: 404 });
1400
+ }
1401
+ return new NextResponse("Storage error", { status: 500 });
1402
+ }
1403
+
1404
+ if (!file) {
1405
+ return new NextResponse("File not found", { status: 404 });
1406
+ }
1407
+
1408
+ // Convert blob to array buffer
1409
+ const arrayBuffer = await file.arrayBuffer();
1410
+
1411
+ // Determine content type from file extension
1412
+ const getContentType = (filename: string): string => {
1413
+ const ext = filename.toLowerCase().split(".").pop();
1414
+ const mimeTypes: Record<string, string> = {
1415
+ // Images
1416
+ jpg: "image/jpeg",
1417
+ jpeg: "image/jpeg",
1418
+ png: "image/png",
1419
+ gif: "image/gif",
1420
+ webp: "image/webp",
1421
+ svg: "image/svg+xml",
1422
+ // Documents
1423
+ pdf: "application/pdf",
1424
+ doc: "application/msword",
1425
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1426
+ xls: "application/vnd.ms-excel",
1427
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1428
+ // Text
1429
+ txt: "text/plain",
1430
+ csv: "text/csv",
1431
+ // Videos
1432
+ mp4: "video/mp4",
1433
+ avi: "video/x-msvideo",
1434
+ mov: "video/quicktime",
1435
+ // Audio
1436
+ mp3: "audio/mpeg",
1437
+ wav: "audio/wav",
1438
+ // Archives
1439
+ zip: "application/zip",
1440
+ rar: "application/x-rar-compressed",
1441
+ };
1442
+ return mimeTypes[ext || ""] || "application/octet-stream";
1443
+ };
1444
+
1445
+ const contentType = getContentType(filePath);
1446
+
1447
+ // Create response with proper headers
1448
+ const response = new NextResponse(arrayBuffer, {
1449
+ status: 200,
1450
+ headers: {
1451
+ "Content-Type": contentType,
1452
+ "Cache-Control": "public, max-age=31536000, immutable", // Cache for 1 year
1453
+ "Content-Length": arrayBuffer.byteLength.toString(),
1454
+ },
1455
+ });
1456
+
1457
+ return response;
1458
+
1459
+ } catch (error) {
1460
+ console.error("Storage proxy error:", error);
1461
+ return new NextResponse("Internal server error", { status: 500 });
1462
+ }
1463
+ }`;
1464
+
1465
+ await fs.writeFile(routePath, routeContent);
1466
+ console.log(chalk.green("✓ app/api/storage/[bucket]/[...path]/route.ts créé"));
1467
+ }
1468
+
1469
+ console.log(chalk.green("✓ Système de proxy storage configuré"));
1470
+ }
@@ -45,10 +45,36 @@ function parseTablesList(input: string): string[] {
45
45
  .filter((t) => t.length > 0);
46
46
  }
47
47
 
48
+ /**
49
+ * Récupère les versions actuelles des packages LastBrain
50
+ */
51
+ function getLastBrainPackageVersions(rootDir: string): { core: string; ui: string } {
52
+ try {
53
+ const corePackageJson = JSON.parse(
54
+ fs.readFileSync(path.join(rootDir, "packages", "core", "package.json"), "utf-8")
55
+ );
56
+ const uiPackageJson = JSON.parse(
57
+ fs.readFileSync(path.join(rootDir, "packages", "ui", "package.json"), "utf-8")
58
+ );
59
+
60
+ return {
61
+ core: `^${corePackageJson.version}`,
62
+ ui: `^${uiPackageJson.version}`,
63
+ };
64
+ } catch (error) {
65
+ console.warn(chalk.yellow("⚠️ Impossible de lire les versions des packages, utilisation des versions par défaut"));
66
+ return {
67
+ core: "^0.1.0",
68
+ ui: "^0.1.4",
69
+ };
70
+ }
71
+ }
72
+
48
73
  /**
49
74
  * Génère le contenu du package.json
50
75
  */
51
- function generatePackageJson(moduleName: string, slug: string): string {
76
+ function generatePackageJson(moduleName: string, slug: string, rootDir: string): string {
77
+ const versions = getLastBrainPackageVersions(rootDir);
52
78
  const buildConfigExport = `./${slug}.build.config`;
53
79
  return JSON.stringify(
54
80
  {
@@ -58,14 +84,14 @@ function generatePackageJson(moduleName: string, slug: string): string {
58
84
  type: "module",
59
85
  main: "dist/index.js",
60
86
  types: "dist/index.d.ts",
61
- files: ["dist", "supabase"],
87
+ files: ["dist", "src", "supabase"],
62
88
  scripts: {
63
89
  build: "tsc -p tsconfig.json",
64
90
  dev: "tsc -p tsconfig.json --watch",
65
91
  },
66
92
  dependencies: {
67
- "@lastbrain/core": "workspace:0.1.0",
68
- "@lastbrain/ui": "workspace:0.1.0",
93
+ "@lastbrain/core": versions.core,
94
+ "@lastbrain/ui": versions.ui,
69
95
  react: "^19.0.0",
70
96
  "lucide-react": "^0.554.0",
71
97
  "react-dom": "^19.0.0",
@@ -560,7 +586,7 @@ async function createModuleStructure(config: ModuleConfig, rootDir: string) {
560
586
  console.log(chalk.yellow(" 📄 package.json"));
561
587
  await fs.writeFile(
562
588
  path.join(moduleDir, "package.json"),
563
- generatePackageJson(config.moduleName, config.slug)
589
+ generatePackageJson(config.moduleName, config.slug, rootDir)
564
590
  );
565
591
 
566
592
  // Créer tsconfig.json