@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.
- package/dist/__tests__/module-registry.test.d.ts +2 -0
- package/dist/__tests__/module-registry.test.d.ts.map +1 -0
- package/dist/__tests__/module-registry.test.js +64 -0
- package/dist/app-shell/(admin)/layout.d.ts +3 -2
- package/dist/app-shell/(admin)/layout.d.ts.map +1 -1
- package/dist/app-shell/(admin)/layout.js +1 -1
- package/dist/app-shell/(auth)/layout.d.ts +3 -2
- package/dist/app-shell/(auth)/layout.d.ts.map +1 -1
- package/dist/app-shell/(auth)/layout.js +1 -1
- package/dist/cli.js +50 -0
- package/dist/layouts/AdminLayout.d.ts +3 -2
- package/dist/layouts/AdminLayout.d.ts.map +1 -1
- package/dist/layouts/AppProviders.d.ts +3 -2
- package/dist/layouts/AppProviders.d.ts.map +1 -1
- package/dist/layouts/AuthLayout.d.ts +3 -2
- package/dist/layouts/AuthLayout.d.ts.map +1 -1
- package/dist/layouts/PublicLayout.d.ts +3 -2
- package/dist/layouts/PublicLayout.d.ts.map +1 -1
- package/dist/layouts/RootLayout.d.ts +3 -2
- package/dist/layouts/RootLayout.d.ts.map +1 -1
- package/dist/scripts/db-init.js +1 -1
- package/dist/scripts/db-migrations-sync.js +5 -5
- package/dist/scripts/init-app.d.ts.map +1 -1
- package/dist/scripts/init-app.js +355 -23
- package/dist/scripts/module-add.d.ts.map +1 -1
- package/dist/scripts/module-add.js +1 -1
- package/dist/scripts/script-runner.d.ts +5 -0
- package/dist/scripts/script-runner.d.ts.map +1 -0
- package/dist/scripts/script-runner.js +25 -0
- package/dist/styles.css +1 -1
- package/dist/templates/DefaultDoc.d.ts.map +1 -1
- package/dist/templates/DefaultDoc.js +16 -1
- package/dist/templates/DocPage.d.ts.map +1 -1
- package/dist/templates/DocPage.js +23 -17
- package/package.json +19 -20
- package/src/__tests__/module-registry.test.ts +74 -0
- package/src/app-shell/(admin)/layout.tsx +5 -3
- package/src/app-shell/(auth)/layout.tsx +5 -3
- package/src/cli.ts +50 -0
- package/src/layouts/AdminLayout.tsx +1 -3
- package/src/layouts/AppProviders.tsx +2 -4
- package/src/layouts/AuthLayout.tsx +1 -3
- package/src/layouts/PublicLayout.tsx +1 -3
- package/src/layouts/RootLayout.tsx +1 -2
- package/src/scripts/db-init.ts +2 -2
- package/src/scripts/db-migrations-sync.ts +1 -1
- package/src/scripts/init-app.ts +366 -23
- package/src/scripts/module-add.ts +1 -3
- package/src/scripts/script-runner.ts +28 -0
- package/src/templates/DefaultDoc.tsx +127 -0
- package/src/templates/DocPage.tsx +83 -49
package/dist/scripts/init-app.js
CHANGED
|
@@ -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
|
|
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
|
-
//
|
|
56
|
+
// 7. Créer la structure Supabase avec migrations
|
|
55
57
|
await createSupabaseStructure(targetDir, force);
|
|
56
|
-
//
|
|
58
|
+
// 8. Ajouter les scripts NPM
|
|
57
59
|
await addScriptsToPackageJson(targetDir);
|
|
58
|
-
//
|
|
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 = "
|
|
904
|
-
if (targetIsInMonorepo) {
|
|
905
|
-
//
|
|
906
|
-
scriptsPrefix = "
|
|
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
|
-
|
|
915
|
-
|
|
916
|
-
"
|
|
917
|
-
|
|
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,
|
|
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
|
-
|
|
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 @@
|
|
|
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
|
+
}
|