create-fluxstack 1.1.0 → 1.4.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.
- package/app/server/backend-only.ts +5 -5
- package/app/server/index.ts +63 -54
- package/app/server/live/FluxStackConfig.ts +43 -39
- package/app/server/live/SystemMonitorIntegration.ts +2 -2
- package/app/server/live/register-components.ts +1 -1
- package/app/server/middleware/errorHandling.ts +6 -4
- package/app/server/routes/config.ts +145 -0
- package/app/server/routes/index.ts +5 -3
- package/config/app.config.ts +113 -0
- package/config/build.config.ts +24 -0
- package/config/database.config.ts +99 -0
- package/config/index.ts +68 -0
- package/config/logger.config.ts +27 -0
- package/config/runtime.config.ts +92 -0
- package/config/server.config.ts +46 -0
- package/config/services.config.ts +130 -0
- package/config/system.config.ts +105 -0
- package/core/build/index.ts +10 -4
- package/core/cli/generators/index.ts +5 -2
- package/core/cli/generators/plugin.ts +290 -0
- package/core/cli/index.ts +117 -15
- package/core/config/env.ts +37 -95
- package/core/config/runtime-config.ts +61 -58
- package/core/config/schema.ts +4 -0
- package/core/framework/server.ts +22 -10
- package/core/plugins/built-in/index.ts +7 -17
- package/core/plugins/built-in/swagger/index.ts +228 -228
- package/core/plugins/built-in/vite/index.ts +374 -358
- package/core/plugins/dependency-manager.ts +5 -5
- package/core/plugins/manager.ts +12 -12
- package/core/plugins/registry.ts +3 -3
- package/core/server/index.ts +0 -1
- package/core/server/live/ComponentRegistry.ts +34 -8
- package/core/server/live/LiveComponentPerformanceMonitor.ts +1 -1
- package/core/server/live/websocket-plugin.ts +434 -434
- package/core/server/middleware/README.md +488 -0
- package/core/server/middleware/elysia-helpers.ts +227 -0
- package/core/server/middleware/index.ts +25 -9
- package/core/server/plugins/static-files-plugin.ts +231 -231
- package/core/utils/config-schema.ts +484 -0
- package/core/utils/env.ts +306 -0
- package/core/utils/helpers.ts +4 -4
- package/core/utils/logger/colors.ts +114 -0
- package/core/utils/logger/config.ts +35 -0
- package/core/utils/logger/formatter.ts +82 -0
- package/core/utils/logger/group-logger.ts +101 -0
- package/core/utils/logger/index.ts +199 -250
- package/core/utils/logger/stack-trace.ts +92 -0
- package/core/utils/logger/startup-banner.ts +92 -0
- package/core/utils/logger/winston-logger.ts +152 -0
- package/core/utils/version.ts +5 -0
- package/create-fluxstack.ts +118 -8
- package/fluxstack.config.ts +2 -2
- package/package.json +117 -115
- package/core/config/env-dynamic.ts +0 -326
- package/core/plugins/built-in/logger/index.ts +0 -180
- package/core/server/plugins/logger.ts +0 -47
- package/core/utils/env-runtime-v2.ts +0 -232
- package/core/utils/env-runtime.ts +0 -259
- package/core/utils/logger/formatters.ts +0 -222
- package/core/utils/logger/middleware.ts +0 -253
- package/core/utils/logger/performance.ts +0 -384
- package/core/utils/logger/transports.ts +0 -365
- package/core/utils/logger.ts +0 -106
|
@@ -3,14 +3,30 @@
|
|
|
3
3
|
* FluxStack middleware infrastructure exports
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
export {
|
|
7
|
-
errorHandlingMiddleware,
|
|
8
|
-
notFoundMiddleware,
|
|
9
|
-
createError,
|
|
10
|
-
asyncHandler
|
|
6
|
+
export {
|
|
7
|
+
errorHandlingMiddleware,
|
|
8
|
+
notFoundMiddleware,
|
|
9
|
+
createError,
|
|
10
|
+
asyncHandler
|
|
11
11
|
} from './errorHandling.js'
|
|
12
12
|
|
|
13
|
-
export type {
|
|
14
|
-
ErrorHandlingOptions,
|
|
15
|
-
FluxStackError
|
|
16
|
-
} from './errorHandling.js'
|
|
13
|
+
export type {
|
|
14
|
+
ErrorHandlingOptions,
|
|
15
|
+
FluxStackError
|
|
16
|
+
} from './errorHandling.js'
|
|
17
|
+
|
|
18
|
+
// Elysia Middleware Helpers
|
|
19
|
+
export {
|
|
20
|
+
createMiddleware,
|
|
21
|
+
createDerive,
|
|
22
|
+
createGuard,
|
|
23
|
+
createRateLimit,
|
|
24
|
+
composeMiddleware,
|
|
25
|
+
isDevelopment,
|
|
26
|
+
isProduction,
|
|
27
|
+
isTest
|
|
28
|
+
} from './elysia-helpers.js'
|
|
29
|
+
|
|
30
|
+
export type {
|
|
31
|
+
MiddlewareOptions
|
|
32
|
+
} from './elysia-helpers.js'
|
|
@@ -1,232 +1,232 @@
|
|
|
1
|
-
// 🔥 FluxStack Static Files Plugin - Serve Public Files
|
|
2
|
-
|
|
3
|
-
import { existsSync, statSync } from 'fs'
|
|
4
|
-
import { join, extname, resolve } from 'path'
|
|
5
|
-
import type { Plugin, PluginContext } from '../plugins/types'
|
|
6
|
-
|
|
7
|
-
export interface StaticFilesConfig {
|
|
8
|
-
publicDir?: string // Default: 'public'
|
|
9
|
-
uploadsDir?: string // Default: 'uploads'
|
|
10
|
-
cacheMaxAge?: number // Default: 1 year in seconds
|
|
11
|
-
enableUploads?: boolean // Default: true
|
|
12
|
-
enablePublic?: boolean // Default: true
|
|
13
|
-
publicRoute?: string // Default: '/public' (can be '/static' in dev)
|
|
14
|
-
uploadsRoute?: string // Default: '/uploads'
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export const staticFilesPlugin: Plugin = {
|
|
18
|
-
name: 'static-files',
|
|
19
|
-
version: '1.0.0',
|
|
20
|
-
description: 'Serve static files and uploads with proper caching and security',
|
|
21
|
-
author: 'FluxStack Team',
|
|
22
|
-
priority: 'normal',
|
|
23
|
-
category: 'core',
|
|
24
|
-
tags: ['static', 'files', 'uploads', 'public'],
|
|
25
|
-
|
|
26
|
-
setup: async (context: PluginContext) => {
|
|
27
|
-
context.logger.
|
|
28
|
-
|
|
29
|
-
const config: StaticFilesConfig = {
|
|
30
|
-
publicDir: 'public',
|
|
31
|
-
uploadsDir: 'uploads',
|
|
32
|
-
cacheMaxAge: 31536000, // 1 year
|
|
33
|
-
enableUploads: true,
|
|
34
|
-
enablePublic: true,
|
|
35
|
-
publicRoute: '/api/static', // Use /api/static in dev to avoid Vite conflicts
|
|
36
|
-
uploadsRoute: '/api/uploads',
|
|
37
|
-
...context.config.staticFiles
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const projectRoot = process.cwd()
|
|
41
|
-
const publicPath = resolve(projectRoot, config.publicDir!)
|
|
42
|
-
const uploadsPath = resolve(projectRoot, config.uploadsDir!)
|
|
43
|
-
|
|
44
|
-
// MIME types mapping
|
|
45
|
-
const getMimeType = (extension: string): string => {
|
|
46
|
-
const mimeTypes: Record<string, string> = {
|
|
47
|
-
// Images
|
|
48
|
-
'.jpg': 'image/jpeg',
|
|
49
|
-
'.jpeg': 'image/jpeg',
|
|
50
|
-
'.png': 'image/png',
|
|
51
|
-
'.gif': 'image/gif',
|
|
52
|
-
'.webp': 'image/webp',
|
|
53
|
-
'.svg': 'image/svg+xml',
|
|
54
|
-
'.ico': 'image/x-icon',
|
|
55
|
-
|
|
56
|
-
// Documents
|
|
57
|
-
'.pdf': 'application/pdf',
|
|
58
|
-
'.txt': 'text/plain',
|
|
59
|
-
'.json': 'application/json',
|
|
60
|
-
'.xml': 'application/xml',
|
|
61
|
-
|
|
62
|
-
// Web assets
|
|
63
|
-
'.css': 'text/css',
|
|
64
|
-
'.js': 'application/javascript',
|
|
65
|
-
'.html': 'text/html',
|
|
66
|
-
'.htm': 'text/html',
|
|
67
|
-
|
|
68
|
-
// Fonts
|
|
69
|
-
'.woff': 'font/woff',
|
|
70
|
-
'.woff2': 'font/woff2',
|
|
71
|
-
'.ttf': 'font/ttf',
|
|
72
|
-
'.otf': 'font/otf',
|
|
73
|
-
|
|
74
|
-
// Audio/Video
|
|
75
|
-
'.mp3': 'audio/mpeg',
|
|
76
|
-
'.mp4': 'video/mp4',
|
|
77
|
-
'.webm': 'video/webm',
|
|
78
|
-
'.ogg': 'audio/ogg'
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return mimeTypes[extension.toLowerCase()] || 'application/octet-stream'
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Security check for path traversal
|
|
85
|
-
const isPathSafe = (filePath: string, basePath: string): boolean => {
|
|
86
|
-
const resolvedPath = resolve(basePath, filePath)
|
|
87
|
-
return resolvedPath.startsWith(basePath)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Generic file serving function
|
|
91
|
-
const serveFile = async (filePath: string, set: any) => {
|
|
92
|
-
try {
|
|
93
|
-
if (!existsSync(filePath)) {
|
|
94
|
-
set.status = 404
|
|
95
|
-
return {
|
|
96
|
-
error: 'File not found',
|
|
97
|
-
path: filePath.replace(projectRoot, ''),
|
|
98
|
-
timestamp: new Date().toISOString()
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const stats = statSync(filePath)
|
|
103
|
-
if (!stats.isFile()) {
|
|
104
|
-
set.status = 404
|
|
105
|
-
return { error: 'Not a file' }
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Set appropriate headers
|
|
109
|
-
const extension = extname(filePath).toLowerCase()
|
|
110
|
-
const mimeType = getMimeType(extension)
|
|
111
|
-
|
|
112
|
-
set.headers['content-type'] = mimeType
|
|
113
|
-
set.headers['content-length'] = stats.size.toString()
|
|
114
|
-
set.headers['last-modified'] = stats.mtime.toUTCString()
|
|
115
|
-
set.headers['cache-control'] = `public, max-age=${config.cacheMaxAge}`
|
|
116
|
-
set.headers['etag'] = `"${stats.mtime.getTime()}-${stats.size}"`
|
|
117
|
-
|
|
118
|
-
// Security headers for images
|
|
119
|
-
if (mimeType.startsWith('image/')) {
|
|
120
|
-
set.headers['x-content-type-options'] = 'nosniff'
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
context.logger.debug(`📁 Serving file: ${filePath.replace(projectRoot, '')}`, {
|
|
124
|
-
size: stats.size,
|
|
125
|
-
mimeType,
|
|
126
|
-
lastModified: stats.mtime
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
return Bun.file(filePath)
|
|
130
|
-
|
|
131
|
-
} catch (error: any) {
|
|
132
|
-
context.logger.error('❌ File serving error:', error.message)
|
|
133
|
-
set.status = 500
|
|
134
|
-
return { error: 'Failed to serve file' }
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Add static file routes
|
|
139
|
-
if (config.enablePublic) {
|
|
140
|
-
const publicRoutePattern = `${config.publicRoute}/*`
|
|
141
|
-
context.app.get(publicRoutePattern, ({ params, set }) => {
|
|
142
|
-
const filePath = params['*'] || ''
|
|
143
|
-
|
|
144
|
-
if (!isPathSafe(filePath, publicPath)) {
|
|
145
|
-
set.status = 400
|
|
146
|
-
return { error: 'Invalid file path' }
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const fullPath = join(publicPath, filePath)
|
|
150
|
-
return serveFile(fullPath, set)
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
context.logger.
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (config.enableUploads) {
|
|
157
|
-
const uploadsRoutePattern = `${config.uploadsRoute}/*`
|
|
158
|
-
context.app.get(uploadsRoutePattern, ({ params, set }) => {
|
|
159
|
-
const filePath = params['*'] || ''
|
|
160
|
-
|
|
161
|
-
if (!isPathSafe(filePath, uploadsPath)) {
|
|
162
|
-
set.status = 400
|
|
163
|
-
return { error: 'Invalid file path' }
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const fullPath = join(uploadsPath, filePath)
|
|
167
|
-
return serveFile(fullPath, set)
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
context.logger.
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Static files info endpoint
|
|
174
|
-
context.app.get('/api/static/info', () => {
|
|
175
|
-
return {
|
|
176
|
-
success: true,
|
|
177
|
-
config: {
|
|
178
|
-
publicDir: config.publicDir,
|
|
179
|
-
uploadsDir: config.uploadsDir,
|
|
180
|
-
enablePublic: config.enablePublic,
|
|
181
|
-
enableUploads: config.enableUploads,
|
|
182
|
-
cacheMaxAge: config.cacheMaxAge
|
|
183
|
-
},
|
|
184
|
-
paths: {
|
|
185
|
-
publicPath,
|
|
186
|
-
uploadsPath,
|
|
187
|
-
publicUrl: config.publicRoute,
|
|
188
|
-
uploadsUrl: config.uploadsRoute
|
|
189
|
-
},
|
|
190
|
-
timestamp: new Date().toISOString()
|
|
191
|
-
}
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
// Create directories if they don't exist
|
|
195
|
-
const { mkdir } = await import('fs/promises')
|
|
196
|
-
|
|
197
|
-
if (config.enablePublic && !existsSync(publicPath)) {
|
|
198
|
-
await mkdir(publicPath, { recursive: true })
|
|
199
|
-
context.logger.
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (config.enableUploads && !existsSync(uploadsPath)) {
|
|
203
|
-
await mkdir(uploadsPath, { recursive: true })
|
|
204
|
-
await mkdir(join(uploadsPath, 'avatars'), { recursive: true })
|
|
205
|
-
context.logger.
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
context.logger.
|
|
209
|
-
publicEnabled: config.enablePublic,
|
|
210
|
-
uploadsEnabled: config.enableUploads,
|
|
211
|
-
publicPath: config.enablePublic ? publicPath : 'disabled',
|
|
212
|
-
uploadsPath: config.enableUploads ? uploadsPath : 'disabled'
|
|
213
|
-
})
|
|
214
|
-
},
|
|
215
|
-
|
|
216
|
-
onServerStart: async (context: PluginContext) => {
|
|
217
|
-
const config = {
|
|
218
|
-
enablePublic: true,
|
|
219
|
-
enableUploads: true,
|
|
220
|
-
publicRoute: '/api/static',
|
|
221
|
-
uploadsRoute: '/api/uploads',
|
|
222
|
-
...context.config.staticFiles
|
|
223
|
-
}
|
|
224
|
-
context.logger.
|
|
225
|
-
routes: [
|
|
226
|
-
config.enablePublic ? `${config.publicRoute}/*` : null,
|
|
227
|
-
config.enableUploads ? `${config.uploadsRoute}/*` : null,
|
|
228
|
-
'/api/static/info'
|
|
229
|
-
].filter(Boolean)
|
|
230
|
-
})
|
|
231
|
-
}
|
|
1
|
+
// 🔥 FluxStack Static Files Plugin - Serve Public Files
|
|
2
|
+
|
|
3
|
+
import { existsSync, statSync } from 'fs'
|
|
4
|
+
import { join, extname, resolve } from 'path'
|
|
5
|
+
import type { Plugin, PluginContext } from '../plugins/types'
|
|
6
|
+
|
|
7
|
+
export interface StaticFilesConfig {
|
|
8
|
+
publicDir?: string // Default: 'public'
|
|
9
|
+
uploadsDir?: string // Default: 'uploads'
|
|
10
|
+
cacheMaxAge?: number // Default: 1 year in seconds
|
|
11
|
+
enableUploads?: boolean // Default: true
|
|
12
|
+
enablePublic?: boolean // Default: true
|
|
13
|
+
publicRoute?: string // Default: '/public' (can be '/static' in dev)
|
|
14
|
+
uploadsRoute?: string // Default: '/uploads'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const staticFilesPlugin: Plugin = {
|
|
18
|
+
name: 'static-files',
|
|
19
|
+
version: '1.0.0',
|
|
20
|
+
description: 'Serve static files and uploads with proper caching and security',
|
|
21
|
+
author: 'FluxStack Team',
|
|
22
|
+
priority: 'normal',
|
|
23
|
+
category: 'core',
|
|
24
|
+
tags: ['static', 'files', 'uploads', 'public'],
|
|
25
|
+
|
|
26
|
+
setup: async (context: PluginContext) => {
|
|
27
|
+
context.logger.debug('📁 Setting up Static Files plugin...')
|
|
28
|
+
|
|
29
|
+
const config: StaticFilesConfig = {
|
|
30
|
+
publicDir: 'public',
|
|
31
|
+
uploadsDir: 'uploads',
|
|
32
|
+
cacheMaxAge: 31536000, // 1 year
|
|
33
|
+
enableUploads: true,
|
|
34
|
+
enablePublic: true,
|
|
35
|
+
publicRoute: '/api/static', // Use /api/static in dev to avoid Vite conflicts
|
|
36
|
+
uploadsRoute: '/api/uploads',
|
|
37
|
+
...context.config.staticFiles
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const projectRoot = process.cwd()
|
|
41
|
+
const publicPath = resolve(projectRoot, config.publicDir!)
|
|
42
|
+
const uploadsPath = resolve(projectRoot, config.uploadsDir!)
|
|
43
|
+
|
|
44
|
+
// MIME types mapping
|
|
45
|
+
const getMimeType = (extension: string): string => {
|
|
46
|
+
const mimeTypes: Record<string, string> = {
|
|
47
|
+
// Images
|
|
48
|
+
'.jpg': 'image/jpeg',
|
|
49
|
+
'.jpeg': 'image/jpeg',
|
|
50
|
+
'.png': 'image/png',
|
|
51
|
+
'.gif': 'image/gif',
|
|
52
|
+
'.webp': 'image/webp',
|
|
53
|
+
'.svg': 'image/svg+xml',
|
|
54
|
+
'.ico': 'image/x-icon',
|
|
55
|
+
|
|
56
|
+
// Documents
|
|
57
|
+
'.pdf': 'application/pdf',
|
|
58
|
+
'.txt': 'text/plain',
|
|
59
|
+
'.json': 'application/json',
|
|
60
|
+
'.xml': 'application/xml',
|
|
61
|
+
|
|
62
|
+
// Web assets
|
|
63
|
+
'.css': 'text/css',
|
|
64
|
+
'.js': 'application/javascript',
|
|
65
|
+
'.html': 'text/html',
|
|
66
|
+
'.htm': 'text/html',
|
|
67
|
+
|
|
68
|
+
// Fonts
|
|
69
|
+
'.woff': 'font/woff',
|
|
70
|
+
'.woff2': 'font/woff2',
|
|
71
|
+
'.ttf': 'font/ttf',
|
|
72
|
+
'.otf': 'font/otf',
|
|
73
|
+
|
|
74
|
+
// Audio/Video
|
|
75
|
+
'.mp3': 'audio/mpeg',
|
|
76
|
+
'.mp4': 'video/mp4',
|
|
77
|
+
'.webm': 'video/webm',
|
|
78
|
+
'.ogg': 'audio/ogg'
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return mimeTypes[extension.toLowerCase()] || 'application/octet-stream'
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Security check for path traversal
|
|
85
|
+
const isPathSafe = (filePath: string, basePath: string): boolean => {
|
|
86
|
+
const resolvedPath = resolve(basePath, filePath)
|
|
87
|
+
return resolvedPath.startsWith(basePath)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Generic file serving function
|
|
91
|
+
const serveFile = async (filePath: string, set: any) => {
|
|
92
|
+
try {
|
|
93
|
+
if (!existsSync(filePath)) {
|
|
94
|
+
set.status = 404
|
|
95
|
+
return {
|
|
96
|
+
error: 'File not found',
|
|
97
|
+
path: filePath.replace(projectRoot, ''),
|
|
98
|
+
timestamp: new Date().toISOString()
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const stats = statSync(filePath)
|
|
103
|
+
if (!stats.isFile()) {
|
|
104
|
+
set.status = 404
|
|
105
|
+
return { error: 'Not a file' }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Set appropriate headers
|
|
109
|
+
const extension = extname(filePath).toLowerCase()
|
|
110
|
+
const mimeType = getMimeType(extension)
|
|
111
|
+
|
|
112
|
+
set.headers['content-type'] = mimeType
|
|
113
|
+
set.headers['content-length'] = stats.size.toString()
|
|
114
|
+
set.headers['last-modified'] = stats.mtime.toUTCString()
|
|
115
|
+
set.headers['cache-control'] = `public, max-age=${config.cacheMaxAge}`
|
|
116
|
+
set.headers['etag'] = `"${stats.mtime.getTime()}-${stats.size}"`
|
|
117
|
+
|
|
118
|
+
// Security headers for images
|
|
119
|
+
if (mimeType.startsWith('image/')) {
|
|
120
|
+
set.headers['x-content-type-options'] = 'nosniff'
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
context.logger.debug(`📁 Serving file: ${filePath.replace(projectRoot, '')}`, {
|
|
124
|
+
size: stats.size,
|
|
125
|
+
mimeType,
|
|
126
|
+
lastModified: stats.mtime
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
return Bun.file(filePath)
|
|
130
|
+
|
|
131
|
+
} catch (error: any) {
|
|
132
|
+
context.logger.error('❌ File serving error:', error.message)
|
|
133
|
+
set.status = 500
|
|
134
|
+
return { error: 'Failed to serve file' }
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Add static file routes
|
|
139
|
+
if (config.enablePublic) {
|
|
140
|
+
const publicRoutePattern = `${config.publicRoute}/*`
|
|
141
|
+
context.app.get(publicRoutePattern, ({ params, set }) => {
|
|
142
|
+
const filePath = params['*'] || ''
|
|
143
|
+
|
|
144
|
+
if (!isPathSafe(filePath, publicPath)) {
|
|
145
|
+
set.status = 400
|
|
146
|
+
return { error: 'Invalid file path' }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const fullPath = join(publicPath, filePath)
|
|
150
|
+
return serveFile(fullPath, set)
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
context.logger.debug(`📁 Public files route enabled: ${publicRoutePattern} → ${config.publicDir}`)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (config.enableUploads) {
|
|
157
|
+
const uploadsRoutePattern = `${config.uploadsRoute}/*`
|
|
158
|
+
context.app.get(uploadsRoutePattern, ({ params, set }) => {
|
|
159
|
+
const filePath = params['*'] || ''
|
|
160
|
+
|
|
161
|
+
if (!isPathSafe(filePath, uploadsPath)) {
|
|
162
|
+
set.status = 400
|
|
163
|
+
return { error: 'Invalid file path' }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const fullPath = join(uploadsPath, filePath)
|
|
167
|
+
return serveFile(fullPath, set)
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
context.logger.debug(`📁 Uploads route enabled: ${uploadsRoutePattern} → ${config.uploadsDir}`)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Static files info endpoint
|
|
174
|
+
context.app.get('/api/static/info', () => {
|
|
175
|
+
return {
|
|
176
|
+
success: true,
|
|
177
|
+
config: {
|
|
178
|
+
publicDir: config.publicDir,
|
|
179
|
+
uploadsDir: config.uploadsDir,
|
|
180
|
+
enablePublic: config.enablePublic,
|
|
181
|
+
enableUploads: config.enableUploads,
|
|
182
|
+
cacheMaxAge: config.cacheMaxAge
|
|
183
|
+
},
|
|
184
|
+
paths: {
|
|
185
|
+
publicPath,
|
|
186
|
+
uploadsPath,
|
|
187
|
+
publicUrl: config.publicRoute,
|
|
188
|
+
uploadsUrl: config.uploadsRoute
|
|
189
|
+
},
|
|
190
|
+
timestamp: new Date().toISOString()
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
// Create directories if they don't exist
|
|
195
|
+
const { mkdir } = await import('fs/promises')
|
|
196
|
+
|
|
197
|
+
if (config.enablePublic && !existsSync(publicPath)) {
|
|
198
|
+
await mkdir(publicPath, { recursive: true })
|
|
199
|
+
context.logger.debug(`📁 Created public directory: ${publicPath}`)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (config.enableUploads && !existsSync(uploadsPath)) {
|
|
203
|
+
await mkdir(uploadsPath, { recursive: true })
|
|
204
|
+
await mkdir(join(uploadsPath, 'avatars'), { recursive: true })
|
|
205
|
+
context.logger.debug(`📁 Created uploads directory: ${uploadsPath}`)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
context.logger.debug('📁 Static Files plugin setup complete', {
|
|
209
|
+
publicEnabled: config.enablePublic,
|
|
210
|
+
uploadsEnabled: config.enableUploads,
|
|
211
|
+
publicPath: config.enablePublic ? publicPath : 'disabled',
|
|
212
|
+
uploadsPath: config.enableUploads ? uploadsPath : 'disabled'
|
|
213
|
+
})
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
onServerStart: async (context: PluginContext) => {
|
|
217
|
+
const config = {
|
|
218
|
+
enablePublic: true,
|
|
219
|
+
enableUploads: true,
|
|
220
|
+
publicRoute: '/api/static',
|
|
221
|
+
uploadsRoute: '/api/uploads',
|
|
222
|
+
...context.config.staticFiles
|
|
223
|
+
}
|
|
224
|
+
context.logger.debug('📁 Static Files plugin ready', {
|
|
225
|
+
routes: [
|
|
226
|
+
config.enablePublic ? `${config.publicRoute}/*` : null,
|
|
227
|
+
config.enableUploads ? `${config.uploadsRoute}/*` : null,
|
|
228
|
+
'/api/static/info'
|
|
229
|
+
].filter(Boolean)
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
232
|
}
|