@lastbrain/app 0.1.23 → 0.1.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/__tests__/module-registry.test.d.ts +2 -0
  2. package/dist/__tests__/module-registry.test.d.ts.map +1 -0
  3. package/dist/__tests__/module-registry.test.js +64 -0
  4. package/dist/app-shell/(admin)/layout.d.ts +3 -2
  5. package/dist/app-shell/(admin)/layout.d.ts.map +1 -1
  6. package/dist/app-shell/(admin)/layout.js +1 -1
  7. package/dist/app-shell/(auth)/layout.d.ts +3 -2
  8. package/dist/app-shell/(auth)/layout.d.ts.map +1 -1
  9. package/dist/app-shell/(auth)/layout.js +1 -1
  10. package/dist/cli.js +50 -0
  11. package/dist/layouts/AdminLayout.d.ts +3 -2
  12. package/dist/layouts/AdminLayout.d.ts.map +1 -1
  13. package/dist/layouts/AppProviders.d.ts +3 -2
  14. package/dist/layouts/AppProviders.d.ts.map +1 -1
  15. package/dist/layouts/AuthLayout.d.ts +3 -2
  16. package/dist/layouts/AuthLayout.d.ts.map +1 -1
  17. package/dist/layouts/PublicLayout.d.ts +3 -2
  18. package/dist/layouts/PublicLayout.d.ts.map +1 -1
  19. package/dist/layouts/RootLayout.d.ts +3 -2
  20. package/dist/layouts/RootLayout.d.ts.map +1 -1
  21. package/dist/scripts/db-init.js +1 -1
  22. package/dist/scripts/db-migrations-sync.js +5 -5
  23. package/dist/scripts/init-app.d.ts.map +1 -1
  24. package/dist/scripts/init-app.js +355 -23
  25. package/dist/scripts/module-add.d.ts.map +1 -1
  26. package/dist/scripts/module-add.js +1 -1
  27. package/dist/scripts/script-runner.d.ts +5 -0
  28. package/dist/scripts/script-runner.d.ts.map +1 -0
  29. package/dist/scripts/script-runner.js +25 -0
  30. package/dist/styles.css +1 -1
  31. package/dist/templates/DefaultDoc.d.ts.map +1 -1
  32. package/dist/templates/DefaultDoc.js +16 -1
  33. package/dist/templates/DocPage.d.ts.map +1 -1
  34. package/dist/templates/DocPage.js +23 -17
  35. package/package.json +19 -20
  36. package/src/__tests__/module-registry.test.ts +74 -0
  37. package/src/app-shell/(admin)/layout.tsx +5 -3
  38. package/src/app-shell/(auth)/layout.tsx +5 -3
  39. package/src/cli.ts +50 -0
  40. package/src/layouts/AdminLayout.tsx +1 -3
  41. package/src/layouts/AppProviders.tsx +2 -4
  42. package/src/layouts/AuthLayout.tsx +1 -3
  43. package/src/layouts/PublicLayout.tsx +1 -3
  44. package/src/layouts/RootLayout.tsx +1 -2
  45. package/src/scripts/db-init.ts +2 -2
  46. package/src/scripts/db-migrations-sync.ts +1 -1
  47. package/src/scripts/init-app.ts +366 -23
  48. package/src/scripts/module-add.ts +1 -3
  49. package/src/scripts/script-runner.ts +28 -0
  50. package/src/templates/DefaultDoc.tsx +127 -0
  51. package/src/templates/DocPage.tsx +83 -49
@@ -48,14 +48,16 @@ export async function initApp(options) {
48
48
  await createNextStructure(targetDir, force, useHeroUI, withAuth);
49
49
  // 4. Créer les fichiers de configuration
50
50
  await createConfigFiles(targetDir, force, useHeroUI);
51
- // 5. Créer .gitignore et .env.local.example
51
+ // 5. Créer le système de proxy storage
52
+ await createStorageProxy(targetDir, force);
53
+ // 6. Créer .gitignore et .env.local.example
52
54
  await createGitIgnore(targetDir, force);
53
55
  await createEnvExample(targetDir, force);
54
- // 6. Créer la structure Supabase avec migrations
56
+ // 7. Créer la structure Supabase avec migrations
55
57
  await createSupabaseStructure(targetDir, force);
56
- // 7. Ajouter les scripts NPM
58
+ // 8. Ajouter les scripts NPM
57
59
  await addScriptsToPackageJson(targetDir);
58
- // 8. Enregistrer les modules sélectionnés
60
+ // 9. Enregistrer les modules sélectionnés
59
61
  if (withAuth || selectedModules.length > 0) {
60
62
  await saveModulesConfig(targetDir, selectedModules, withAuth);
61
63
  }
@@ -221,6 +223,7 @@ async function addDependencies(targetDir, useHeroUI, withAuth, selectedModules =
221
223
  // Ajouter les dépendances HeroUI si nécessaire
222
224
  if (useHeroUI) {
223
225
  // Tous les packages HeroUI nécessaires (car @lastbrain/ui les ré-exporte)
226
+ requiredDeps["@heroui/system"] = "^2.4.23";
224
227
  requiredDeps["@heroui/theme"] = "^2.4.23";
225
228
  requiredDeps["@heroui/accordion"] = "^2.2.24";
226
229
  requiredDeps["@heroui/alert"] = "^2.2.27";
@@ -900,31 +903,24 @@ async function addScriptsToPackageJson(targetDir) {
900
903
  // Détecter si le projet cible est dans un workspace
901
904
  const targetIsInMonorepo = fs.existsSync(path.join(targetDir, "../../../packages/core/package.json")) ||
902
905
  fs.existsSync(path.join(targetDir, "../../packages/core/package.json"));
903
- let scriptsPrefix = "node node_modules/@lastbrain/app/dist/scripts/";
904
- if (targetIsInMonorepo) {
905
- // Dans un monorepo, utiliser pnpm exec pour résoudre correctement les workspace packages
906
- scriptsPrefix = "pnpm exec lastbrain ";
906
+ let scriptsPrefix = "lastbrain";
907
+ if (!targetIsInMonorepo) {
908
+ // Hors monorepo, utiliser le chemin direct vers le CLI
909
+ scriptsPrefix = "node node_modules/@lastbrain/app/dist/cli.js";
907
910
  }
908
911
  const scripts = {
912
+ predev: targetIsInMonorepo
913
+ ? "pnpm --filter @lastbrain/core build && pnpm --filter @lastbrain/ui build && pnpm --filter @lastbrain/module-auth build && pnpm --filter @lastbrain/module-ai build"
914
+ : "echo 'No prebuild needed outside monorepo'",
909
915
  dev: "next dev",
910
916
  build: "next build",
911
917
  start: "next start",
912
918
  lint: "next lint",
913
- lastbrain: targetIsInMonorepo
914
- ? "pnpm exec lastbrain"
915
- : "node node_modules/@lastbrain/app/dist/cli.js",
916
- "build:modules": targetIsInMonorepo
917
- ? "pnpm exec lastbrain module:build"
918
- : "node node_modules/@lastbrain/app/dist/scripts/module-build.js",
919
- "db:migrations:sync": targetIsInMonorepo
920
- ? "pnpm exec lastbrain db:migrations:sync"
921
- : "node node_modules/@lastbrain/app/dist/scripts/db-migrations-sync.js",
922
- "db:init": targetIsInMonorepo
923
- ? "pnpm exec lastbrain db:init"
924
- : "node node_modules/@lastbrain/app/dist/scripts/db-init.js",
925
- "readme:create": targetIsInMonorepo
926
- ? "pnpm exec lastbrain readme:create"
927
- : "node node_modules/@lastbrain/app/dist/scripts/readme-build.js",
919
+ lastbrain: targetIsInMonorepo ? "lastbrain" : "node node_modules/@lastbrain/app/dist/cli.js",
920
+ "build:modules": `${scriptsPrefix} module:build`,
921
+ "db:migrations:sync": `${scriptsPrefix} db:migrations:sync`,
922
+ "db:init": `${scriptsPrefix} db:init`,
923
+ "readme:create": `${scriptsPrefix} readme:create`,
928
924
  };
929
925
  pkg.scripts = { ...pkg.scripts, ...scripts };
930
926
  await fs.writeJson(pkgPath, pkg, { spaces: 2 });
@@ -957,3 +953,339 @@ async function saveModulesConfig(targetDir, selectedModules, withAuth) {
957
953
  await fs.writeJson(modulesConfigPath, { modules }, { spaces: 2 });
958
954
  console.log(chalk.green("✓ Configuration des modules sauvegardée"));
959
955
  }
956
+ async function createStorageProxy(targetDir, force) {
957
+ console.log(chalk.yellow("\n🗂️ Création du système de proxy storage..."));
958
+ // Créer le dossier lib
959
+ const libDir = path.join(targetDir, "lib");
960
+ await fs.ensureDir(libDir);
961
+ // 1. Créer lib/bucket-config.ts
962
+ const bucketConfigPath = path.join(libDir, "bucket-config.ts");
963
+ if (!fs.existsSync(bucketConfigPath) || force) {
964
+ const bucketConfigContent = `/**
965
+ * Storage configuration for buckets and access control
966
+ */
967
+
968
+ export interface BucketConfig {
969
+ name: string;
970
+ isPublic: boolean;
971
+ description: string;
972
+ allowedFileTypes?: string[];
973
+ maxFileSize?: number; // in bytes
974
+ customAccessControl?: (userId: string, filePath: string) => boolean;
975
+ }
976
+
977
+ export const BUCKET_CONFIGS: Record<string, BucketConfig> = {
978
+ avatar: {
979
+ name: "avatar",
980
+ isPublic: true,
981
+ description: "User profile pictures and avatars",
982
+ allowedFileTypes: ["image/jpeg", "image/png", "image/webp", "image/gif"],
983
+ maxFileSize: 10 * 1024 * 1024, // 10MB
984
+ },
985
+ app: {
986
+ name: "app",
987
+ isPublic: false,
988
+ description: "Private user files and documents",
989
+ maxFileSize: 100 * 1024 * 1024, // 100MB
990
+ customAccessControl: (userId: string, filePath: string) => {
991
+ // Users can only access files in their own folder (app/{userId}/...)
992
+ return filePath.startsWith(\`\${userId}/\`);
993
+ },
994
+ },
995
+ // Example for future buckets:
996
+ // public: {
997
+ // name: "public",
998
+ // isPublic: true,
999
+ // description: "Publicly accessible files like logos, banners",
1000
+ // allowedFileTypes: ["image/jpeg", "image/png", "image/webp", "application/pdf"],
1001
+ // maxFileSize: 50 * 1024 * 1024, // 50MB
1002
+ // },
1003
+ // documents: {
1004
+ // name: "documents",
1005
+ // isPublic: false,
1006
+ // description: "Private documents requiring authentication",
1007
+ // allowedFileTypes: ["application/pdf", "application/msword", "text/plain"],
1008
+ // maxFileSize: 25 * 1024 * 1024, // 25MB
1009
+ // },
1010
+ };
1011
+
1012
+ /**
1013
+ * Get bucket configuration
1014
+ */
1015
+ export function getBucketConfig(bucketName: string): BucketConfig | null {
1016
+ return BUCKET_CONFIGS[bucketName] || null;
1017
+ }
1018
+
1019
+ /**
1020
+ * Check if bucket is public
1021
+ */
1022
+ export function isPublicBucket(bucketName: string): boolean {
1023
+ const config = getBucketConfig(bucketName);
1024
+ return config?.isPublic ?? false;
1025
+ }
1026
+
1027
+ /**
1028
+ * Check if user has access to a specific file
1029
+ */
1030
+ export function hasFileAccess(bucketName: string, userId: string, filePath: string): boolean {
1031
+ const config = getBucketConfig(bucketName);
1032
+ if (!config) return false;
1033
+
1034
+ // Public buckets are accessible to everyone
1035
+ if (config.isPublic) return true;
1036
+
1037
+ // Private buckets require authentication
1038
+ if (!userId) return false;
1039
+
1040
+ // Apply custom access control if defined
1041
+ if (config.customAccessControl) {
1042
+ return config.customAccessControl(userId, filePath);
1043
+ }
1044
+
1045
+ return true;
1046
+ }
1047
+
1048
+ /**
1049
+ * Validate file type for bucket
1050
+ */
1051
+ export function isValidFileType(bucketName: string, contentType: string): boolean {
1052
+ const config = getBucketConfig(bucketName);
1053
+ if (!config || !config.allowedFileTypes) return true;
1054
+
1055
+ return config.allowedFileTypes.includes(contentType);
1056
+ }
1057
+
1058
+ /**
1059
+ * Check if file size is within bucket limits
1060
+ */
1061
+ export function isValidFileSize(bucketName: string, fileSize: number): boolean {
1062
+ const config = getBucketConfig(bucketName);
1063
+ if (!config || !config.maxFileSize) return true;
1064
+
1065
+ return fileSize <= config.maxFileSize;
1066
+ }`;
1067
+ await fs.writeFile(bucketConfigPath, bucketConfigContent);
1068
+ console.log(chalk.green("✓ lib/bucket-config.ts créé"));
1069
+ }
1070
+ // 2. Créer lib/storage.ts
1071
+ const storagePath = path.join(libDir, "storage.ts");
1072
+ if (!fs.existsSync(storagePath) || force) {
1073
+ const storageContent = `/**
1074
+ * Build storage proxy URL for files in Supabase buckets
1075
+ *
1076
+ * @param bucket - The bucket name (e.g., "avatar", "app")
1077
+ * @param path - The file path within the bucket
1078
+ * @returns Proxied URL (e.g., "/api/storage/avatar/user_128_123456.webp")
1079
+ */
1080
+ export function buildStorageUrl(bucket: string, path: string): string {
1081
+ // Remove leading slash if present
1082
+ const cleanPath = path.startsWith("/") ? path.slice(1) : path;
1083
+
1084
+ // Remove bucket prefix from path if present (e.g., "avatar/file.jpg" -> "file.jpg")
1085
+ const pathWithoutBucket = cleanPath.startsWith(bucket + "/")
1086
+ ? cleanPath.slice(bucket.length + 1)
1087
+ : cleanPath;
1088
+
1089
+ return \`/api/storage/\${bucket}/\${pathWithoutBucket}\`;
1090
+ }
1091
+
1092
+ /**
1093
+ * Extract bucket and path from a storage URL
1094
+ *
1095
+ * @param url - Storage URL (can be proxied URL or Supabase public URL)
1096
+ * @returns Object with bucket and path, or null if not a valid storage URL
1097
+ */
1098
+ export function parseStorageUrl(url: string): { bucket: string; path: string } | null {
1099
+ // Handle proxy URLs like "/api/storage/avatar/file.jpg"
1100
+ const proxyMatch = url.match(/^\\/api\\/storage\\/([^\\/]+)\\/(.+)$/);
1101
+ if (proxyMatch) {
1102
+ return {
1103
+ bucket: proxyMatch[1],
1104
+ path: proxyMatch[2]
1105
+ };
1106
+ }
1107
+
1108
+ // Handle Supabase public URLs
1109
+ const supabaseMatch = url.match(/\\/storage\\/v1\\/object\\/public\\/([^\\/]+)\\/(.+)$/);
1110
+ if (supabaseMatch) {
1111
+ return {
1112
+ bucket: supabaseMatch[1],
1113
+ path: supabaseMatch[2]
1114
+ };
1115
+ }
1116
+
1117
+ return null;
1118
+ }
1119
+
1120
+ /**
1121
+ * Convert a Supabase storage path to proxy URL
1122
+ *
1123
+ * @param storagePath - Path like "avatar/file.jpg" or "app/user/file.pdf"
1124
+ * @returns Proxied URL
1125
+ */
1126
+ export function storagePathToProxyUrl(storagePath: string): string {
1127
+ const parts = storagePath.split("/");
1128
+ if (parts.length < 2) {
1129
+ throw new Error("Invalid storage path format");
1130
+ }
1131
+
1132
+ const bucket = parts[0];
1133
+ const path = parts.slice(1).join("/");
1134
+
1135
+ return buildStorageUrl(bucket, path);
1136
+ }
1137
+
1138
+ /**
1139
+ * List of public buckets that don't require authentication
1140
+ */
1141
+ export const PUBLIC_BUCKETS = ["avatar"];
1142
+
1143
+ /**
1144
+ * List of private buckets that require authentication
1145
+ */
1146
+ export const PRIVATE_BUCKETS = ["app"];
1147
+
1148
+ /**
1149
+ * Check if a bucket is public
1150
+ */
1151
+ export function isPublicBucket(bucket: string): boolean {
1152
+ return PUBLIC_BUCKETS.includes(bucket);
1153
+ }
1154
+
1155
+ /**
1156
+ * Check if a bucket is private
1157
+ */
1158
+ export function isPrivateBucket(bucket: string): boolean {
1159
+ return PRIVATE_BUCKETS.includes(bucket);
1160
+ }`;
1161
+ await fs.writeFile(storagePath, storageContent);
1162
+ console.log(chalk.green("✓ lib/storage.ts créé"));
1163
+ }
1164
+ // 3. Créer app/api/storage/[bucket]/[...path]/route.ts
1165
+ const apiStorageDir = path.join(targetDir, "app", "api", "storage", "[bucket]", "[...path]");
1166
+ await fs.ensureDir(apiStorageDir);
1167
+ const routePath = path.join(apiStorageDir, "route.ts");
1168
+ if (!fs.existsSync(routePath) || force) {
1169
+ const routeContent = `import { NextRequest, NextResponse } from "next/server";
1170
+ import { getSupabaseServerClient } from "@lastbrain/core/server";
1171
+ import { getBucketConfig, hasFileAccess } from "@/lib/bucket-config";
1172
+
1173
+ /**
1174
+ * GET /api/storage/[bucket]/[...path]
1175
+ * Proxy for Supabase Storage files with clean URLs and access control
1176
+ *
1177
+ * Examples:
1178
+ * - /api/storage/avatar/user_128_123456.webp
1179
+ * - /api/storage/app/user/documents/file.pdf
1180
+ */
1181
+ export async function GET(
1182
+ request: NextRequest,
1183
+ props: { params: Promise<{ bucket: string; path: string[] }> }
1184
+ ) {
1185
+ try {
1186
+ const { bucket, path } = await props.params;
1187
+ const filePath = path.join("/");
1188
+
1189
+ // Check if bucket exists in our configuration
1190
+ const bucketConfig = getBucketConfig(bucket);
1191
+ if (!bucketConfig) {
1192
+ return new NextResponse("Bucket not allowed", { status: 403 });
1193
+ }
1194
+
1195
+ const supabase = await getSupabaseServerClient();
1196
+ let userId: string | null = null;
1197
+
1198
+ // Get user for private buckets or custom access control
1199
+ if (!bucketConfig.isPublic) {
1200
+ const { data: { user }, error: authError } = await supabase.auth.getUser();
1201
+
1202
+ if (authError || !user) {
1203
+ return new NextResponse("Unauthorized", { status: 401 });
1204
+ }
1205
+
1206
+ userId = user.id;
1207
+ }
1208
+
1209
+ // Check file access permissions
1210
+ if (!hasFileAccess(bucket, userId || "", filePath)) {
1211
+ return new NextResponse("Forbidden - Access denied to this file", { status: 403 });
1212
+ }
1213
+
1214
+ // Get file from Supabase Storage
1215
+ const { data: file, error } = await supabase.storage
1216
+ .from(bucket)
1217
+ .download(filePath);
1218
+
1219
+ if (error) {
1220
+ console.error("Storage download error:", error);
1221
+ if (error.message.includes("not found")) {
1222
+ return new NextResponse("File not found", { status: 404 });
1223
+ }
1224
+ return new NextResponse("Storage error", { status: 500 });
1225
+ }
1226
+
1227
+ if (!file) {
1228
+ return new NextResponse("File not found", { status: 404 });
1229
+ }
1230
+
1231
+ // Convert blob to array buffer
1232
+ const arrayBuffer = await file.arrayBuffer();
1233
+
1234
+ // Determine content type from file extension
1235
+ const getContentType = (filename: string): string => {
1236
+ const ext = filename.toLowerCase().split(".").pop();
1237
+ const mimeTypes: Record<string, string> = {
1238
+ // Images
1239
+ jpg: "image/jpeg",
1240
+ jpeg: "image/jpeg",
1241
+ png: "image/png",
1242
+ gif: "image/gif",
1243
+ webp: "image/webp",
1244
+ svg: "image/svg+xml",
1245
+ // Documents
1246
+ pdf: "application/pdf",
1247
+ doc: "application/msword",
1248
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1249
+ xls: "application/vnd.ms-excel",
1250
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1251
+ // Text
1252
+ txt: "text/plain",
1253
+ csv: "text/csv",
1254
+ // Videos
1255
+ mp4: "video/mp4",
1256
+ avi: "video/x-msvideo",
1257
+ mov: "video/quicktime",
1258
+ // Audio
1259
+ mp3: "audio/mpeg",
1260
+ wav: "audio/wav",
1261
+ // Archives
1262
+ zip: "application/zip",
1263
+ rar: "application/x-rar-compressed",
1264
+ };
1265
+ return mimeTypes[ext || ""] || "application/octet-stream";
1266
+ };
1267
+
1268
+ const contentType = getContentType(filePath);
1269
+
1270
+ // Create response with proper headers
1271
+ const response = new NextResponse(arrayBuffer, {
1272
+ status: 200,
1273
+ headers: {
1274
+ "Content-Type": contentType,
1275
+ "Cache-Control": "public, max-age=31536000, immutable", // Cache for 1 year
1276
+ "Content-Length": arrayBuffer.byteLength.toString(),
1277
+ },
1278
+ });
1279
+
1280
+ return response;
1281
+
1282
+ } catch (error) {
1283
+ console.error("Storage proxy error:", error);
1284
+ return new NextResponse("Internal server error", { status: 500 });
1285
+ }
1286
+ }`;
1287
+ await fs.writeFile(routePath, routeContent);
1288
+ console.log(chalk.green("✓ app/api/storage/[bucket]/[...path]/route.ts créé"));
1289
+ }
1290
+ console.log(chalk.green("✓ Système de proxy storage configuré"));
1291
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"module-add.d.ts","sourceRoot":"","sources":["../../src/scripts/module-add.ts"],"names":[],"mappings":"AAMA,UAAU,gBAAgB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,OAAO,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAGD,eAAO,MAAM,iBAAiB,EAAE,gBAAgB,EAsB/C,CAAC;AAEF,wBAAsB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,iBAwMpE"}
1
+ {"version":3,"file":"module-add.d.ts","sourceRoot":"","sources":["../../src/scripts/module-add.ts"],"names":[],"mappings":"AAMA,UAAU,gBAAgB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,OAAO,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAGD,eAAO,MAAM,iBAAiB,EAAE,gBAAgB,EAsB/C,CAAC;AAEF,wBAAsB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,iBAsMpE"}
@@ -63,7 +63,7 @@ export async function addModule(moduleName, targetDir) {
63
63
  process.exit(1);
64
64
  }
65
65
  // 5. Copier les migrations du module
66
- let copiedMigrationFiles = [];
66
+ const copiedMigrationFiles = [];
67
67
  if (module.hasMigrations) {
68
68
  console.log(chalk.yellow("\n📋 Copie des migrations du module..."));
69
69
  // Trouver le chemin du module installé
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Exécute un script via node en ajustant le chemin
3
+ */
4
+ export declare function runScript(scriptName: string, args?: string[]): Promise<void>;
5
+ //# sourceMappingURL=script-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"script-runner.d.ts","sourceRoot":"","sources":["../../src/scripts/script-runner.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,wBAAsB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,EAAO,iBAqBtE"}
@@ -0,0 +1,25 @@
1
+ import { spawn } from "child_process";
2
+ import path from "path";
3
+ /**
4
+ * Exécute un script via node en ajustant le chemin
5
+ */
6
+ export async function runScript(scriptName, args = []) {
7
+ return new Promise((resolve, reject) => {
8
+ const scriptPath = path.join(__dirname, `${scriptName}.js`);
9
+ const child = spawn("node", [scriptPath, ...args], {
10
+ stdio: "inherit",
11
+ env: { ...process.env, PROJECT_ROOT: process.cwd() }
12
+ });
13
+ child.on("close", (code) => {
14
+ if (code === 0) {
15
+ resolve();
16
+ }
17
+ else {
18
+ reject(new Error(`Script ${scriptName} exited with code ${code}`));
19
+ }
20
+ });
21
+ child.on("error", (error) => {
22
+ reject(error);
23
+ });
24
+ });
25
+ }