create-fluxstack 1.8.3 → 1.10.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/LIVE_COMPONENTS_REVIEW.md +781 -0
- package/README.md +653 -275
- package/app/client/src/App.tsx +39 -43
- package/app/client/src/lib/eden-api.ts +2 -7
- package/app/client/src/live/FileUploadExample.tsx +359 -0
- package/app/client/src/live/MinimalLiveClock.tsx +47 -0
- package/app/client/src/live/QuickUploadTest.tsx +193 -0
- package/app/client/src/main.tsx +10 -10
- package/app/client/src/vite-env.d.ts +1 -1
- package/app/client/tsconfig.app.json +45 -44
- package/app/client/tsconfig.node.json +25 -25
- package/app/server/index.ts +30 -103
- package/app/server/live/LiveFileUploadComponent.ts +77 -0
- package/app/server/live/register-components.ts +19 -19
- package/core/build/bundler.ts +202 -55
- package/core/build/index.ts +126 -2
- package/core/build/live-components-generator.ts +68 -1
- package/core/cli/generators/plugin.ts +6 -6
- package/core/cli/index.ts +232 -4
- package/core/client/LiveComponentsProvider.tsx +3 -9
- package/core/client/hooks/AdaptiveChunkSizer.ts +215 -0
- package/core/client/hooks/useChunkedUpload.ts +112 -61
- package/core/client/hooks/useHybridLiveComponent.ts +80 -26
- package/core/client/hooks/useTypedLiveComponent.ts +133 -0
- package/core/client/hooks/useWebSocket.ts +4 -16
- package/core/client/index.ts +20 -2
- package/core/framework/server.ts +181 -8
- package/core/live/ComponentRegistry.ts +5 -1
- package/core/plugins/built-in/index.ts +8 -5
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +55 -63
- package/core/plugins/built-in/vite/index.ts +75 -187
- package/core/plugins/built-in/vite/vite-dev.ts +88 -0
- package/core/plugins/registry.ts +54 -2
- package/core/plugins/types.ts +86 -2
- package/core/server/index.ts +1 -2
- package/core/server/live/ComponentRegistry.ts +14 -5
- package/core/server/live/FileUploadManager.ts +22 -25
- package/core/server/live/auto-generated-components.ts +29 -26
- package/core/server/live/websocket-plugin.ts +19 -5
- package/core/server/plugins/static-files-plugin.ts +49 -240
- package/core/server/plugins/swagger.ts +33 -33
- package/core/types/build.ts +22 -0
- package/core/types/plugin.ts +9 -1
- package/core/types/types.ts +137 -0
- package/core/utils/logger/startup-banner.ts +20 -4
- package/core/utils/version.ts +6 -6
- package/create-fluxstack.ts +7 -7
- package/eslint.config.js +23 -23
- package/package.json +3 -2
- package/plugins/crypto-auth/server/middlewares.ts +19 -19
- package/tsconfig.json +52 -51
- package/workspace.json +5 -5
|
@@ -12,9 +12,9 @@ import type {
|
|
|
12
12
|
|
|
13
13
|
export class FileUploadManager {
|
|
14
14
|
private activeUploads = new Map<string, ActiveUpload>()
|
|
15
|
-
private readonly maxUploadSize =
|
|
15
|
+
private readonly maxUploadSize = 500 * 1024 * 1024 // 500MB max (aceita qualquer arquivo)
|
|
16
16
|
private readonly chunkTimeout = 30000 // 30 seconds timeout per chunk
|
|
17
|
-
private readonly allowedTypes = [
|
|
17
|
+
private readonly allowedTypes: string[] = [] // Array vazio = aceita todos os tipos de arquivo
|
|
18
18
|
|
|
19
19
|
constructor() {
|
|
20
20
|
// Cleanup stale uploads every 5 minutes
|
|
@@ -25,12 +25,7 @@ export class FileUploadManager {
|
|
|
25
25
|
try {
|
|
26
26
|
const { uploadId, componentId, filename, fileType, fileSize, chunkSize = 64 * 1024 } = message
|
|
27
27
|
|
|
28
|
-
// Validate file
|
|
29
|
-
if (!this.allowedTypes.includes(fileType)) {
|
|
30
|
-
throw new Error(`Invalid file type: ${fileType}. Allowed: ${this.allowedTypes.join(', ')}`)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Validate file size
|
|
28
|
+
// Validate file size (sem restrição de tipo)
|
|
34
29
|
if (fileSize > this.maxUploadSize) {
|
|
35
30
|
throw new Error(`File too large: ${fileSize} bytes. Max: ${this.maxUploadSize} bytes`)
|
|
36
31
|
}
|
|
@@ -52,6 +47,7 @@ export class FileUploadManager {
|
|
|
52
47
|
fileSize,
|
|
53
48
|
totalChunks,
|
|
54
49
|
receivedChunks: new Map(),
|
|
50
|
+
bytesReceived: 0, // Track actual bytes for adaptive chunking
|
|
55
51
|
startTime: Date.now(),
|
|
56
52
|
lastChunkTime: Date.now()
|
|
57
53
|
}
|
|
@@ -97,16 +93,20 @@ export class FileUploadManager {
|
|
|
97
93
|
upload.receivedChunks.set(chunkIndex, data)
|
|
98
94
|
upload.lastChunkTime = Date.now()
|
|
99
95
|
|
|
100
|
-
|
|
96
|
+
// Track actual bytes received (decode base64 to get real size)
|
|
97
|
+
const chunkBytes = Buffer.from(data, 'base64').length
|
|
98
|
+
upload.bytesReceived += chunkBytes
|
|
99
|
+
|
|
100
|
+
console.log(`📦 Received chunk ${chunkIndex + 1}/${totalChunks} for upload ${uploadId} (${chunkBytes} bytes, total: ${upload.bytesReceived}/${upload.fileSize})`)
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
// Calculate progress
|
|
104
|
-
const progress = (upload.
|
|
105
|
-
const bytesUploaded = upload.
|
|
103
|
+
// Calculate progress based on actual bytes received (supports adaptive chunking)
|
|
104
|
+
const progress = (upload.bytesReceived / upload.fileSize) * 100
|
|
105
|
+
const bytesUploaded = upload.bytesReceived
|
|
106
106
|
|
|
107
|
-
//
|
|
108
|
-
if (upload.
|
|
109
|
-
|
|
107
|
+
// Log completion status (but don't finalize until COMPLETE message)
|
|
108
|
+
if (upload.bytesReceived >= upload.fileSize) {
|
|
109
|
+
console.log(`✅ All bytes received for upload ${uploadId} (${upload.bytesReceived}/${upload.fileSize}), waiting for COMPLETE message`)
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
return {
|
|
@@ -152,19 +152,16 @@ export class FileUploadManager {
|
|
|
152
152
|
throw new Error(`Upload ${uploadId} not found`)
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
console.log(`✅ Upload
|
|
155
|
+
console.log(`✅ Upload completion requested: ${uploadId}`)
|
|
156
156
|
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
missingChunks.push(i)
|
|
162
|
-
}
|
|
157
|
+
// Validate bytes received (supports adaptive chunking)
|
|
158
|
+
if (upload.bytesReceived !== upload.fileSize) {
|
|
159
|
+
const bytesShort = upload.fileSize - upload.bytesReceived
|
|
160
|
+
throw new Error(`Incomplete upload: received ${upload.bytesReceived}/${upload.fileSize} bytes (${bytesShort} bytes short)`)
|
|
163
161
|
}
|
|
164
162
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
163
|
+
console.log(`✅ Upload validation passed: ${uploadId} (${upload.bytesReceived} bytes)`)
|
|
164
|
+
|
|
168
165
|
|
|
169
166
|
// Assemble file from chunks
|
|
170
167
|
const fileUrl = await this.assembleFile(upload)
|
|
@@ -1,26 +1,29 @@
|
|
|
1
|
-
// 🔥 Auto-generated Live Components Registration
|
|
2
|
-
// This file is automatically generated during build time - DO NOT EDIT MANUALLY
|
|
3
|
-
// Generated at: 2025-11-
|
|
4
|
-
|
|
5
|
-
import { LiveClockComponent } from "@/app/server/live/LiveClockComponent"
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
console.
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
1
|
+
// 🔥 Auto-generated Live Components Registration
|
|
2
|
+
// This file is automatically generated during build time - DO NOT EDIT MANUALLY
|
|
3
|
+
// Generated at: 2025-11-23T11:09:30.419Z
|
|
4
|
+
|
|
5
|
+
import { LiveClockComponent } from "@/app/server/live/LiveClockComponent"
|
|
6
|
+
import { LiveFileUploadComponent } from "@/app/server/live/LiveFileUploadComponent"
|
|
7
|
+
import { componentRegistry } from "@/core/server/live/ComponentRegistry"
|
|
8
|
+
|
|
9
|
+
// Register all components statically for production bundle
|
|
10
|
+
function registerAllComponents() {
|
|
11
|
+
try {
|
|
12
|
+
// Auto-generated component registrations
|
|
13
|
+
componentRegistry.registerComponentClass('LiveClock', LiveClockComponent)
|
|
14
|
+
componentRegistry.registerComponentClass('LiveFileUpload', LiveFileUploadComponent)
|
|
15
|
+
|
|
16
|
+
console.log('📝 Live components registered successfully! (2 components)')
|
|
17
|
+
} catch (error) {
|
|
18
|
+
console.warn('⚠️ Error registering components:', error)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Auto-register components
|
|
23
|
+
registerAllComponents()
|
|
24
|
+
|
|
25
|
+
// Export all components to ensure they're included in the bundle
|
|
26
|
+
export {
|
|
27
|
+
LiveClockComponent,
|
|
28
|
+
LiveFileUploadComponent
|
|
29
|
+
}
|
|
@@ -619,11 +619,17 @@ async function handleFileUploadStart(ws: any, message: FileUploadStartMessage) {
|
|
|
619
619
|
|
|
620
620
|
async function handleFileUploadChunk(ws: any, message: FileUploadChunkMessage) {
|
|
621
621
|
console.log(`📦 Receiving chunk ${message.chunkIndex + 1} for upload ${message.uploadId}`)
|
|
622
|
-
|
|
622
|
+
|
|
623
623
|
const progressResponse = await fileUploadManager.receiveChunk(message, ws)
|
|
624
|
-
|
|
624
|
+
|
|
625
625
|
if (progressResponse) {
|
|
626
|
-
|
|
626
|
+
// Add requestId to response so client can correlate it
|
|
627
|
+
const responseWithRequestId = {
|
|
628
|
+
...progressResponse,
|
|
629
|
+
requestId: message.requestId,
|
|
630
|
+
success: true
|
|
631
|
+
}
|
|
632
|
+
ws.send(JSON.stringify(responseWithRequestId))
|
|
627
633
|
} else {
|
|
628
634
|
// Send error response
|
|
629
635
|
const errorResponse = {
|
|
@@ -632,6 +638,7 @@ async function handleFileUploadChunk(ws: any, message: FileUploadChunkMessage) {
|
|
|
632
638
|
uploadId: message.uploadId,
|
|
633
639
|
error: 'Failed to process chunk',
|
|
634
640
|
requestId: message.requestId,
|
|
641
|
+
success: false,
|
|
635
642
|
timestamp: Date.now()
|
|
636
643
|
}
|
|
637
644
|
ws.send(JSON.stringify(errorResponse))
|
|
@@ -640,7 +647,14 @@ async function handleFileUploadChunk(ws: any, message: FileUploadChunkMessage) {
|
|
|
640
647
|
|
|
641
648
|
async function handleFileUploadComplete(ws: any, message: FileUploadCompleteMessage) {
|
|
642
649
|
console.log('✅ Completing file upload:', message.uploadId)
|
|
643
|
-
|
|
650
|
+
|
|
644
651
|
const completeResponse = await fileUploadManager.completeUpload(message)
|
|
645
|
-
|
|
652
|
+
|
|
653
|
+
// Add requestId to response so client can correlate it
|
|
654
|
+
const responseWithRequestId = {
|
|
655
|
+
...completeResponse,
|
|
656
|
+
requestId: message.requestId
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
ws.send(JSON.stringify(responseWithRequestId))
|
|
646
660
|
}
|
|
@@ -1,260 +1,69 @@
|
|
|
1
|
-
// 🔥 FluxStack Static Files Plugin - Serve Public Files
|
|
1
|
+
// 🔥 FluxStack Static Files Plugin - Serve Public Files & Uploads
|
|
2
2
|
|
|
3
3
|
import { existsSync, statSync } from 'fs'
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
// Response schema for static files info endpoint
|
|
9
|
-
const StaticFilesInfoSchema = t.Object({
|
|
10
|
-
success: t.Boolean(),
|
|
11
|
-
config: t.Object({
|
|
12
|
-
publicDir: t.String(),
|
|
13
|
-
uploadsDir: t.String(),
|
|
14
|
-
enablePublic: t.Boolean(),
|
|
15
|
-
enableUploads: t.Boolean(),
|
|
16
|
-
cacheMaxAge: t.Number()
|
|
17
|
-
}),
|
|
18
|
-
paths: t.Object({
|
|
19
|
-
publicPath: t.String(),
|
|
20
|
-
uploadsPath: t.String(),
|
|
21
|
-
publicUrl: t.String(),
|
|
22
|
-
uploadsUrl: t.String()
|
|
23
|
-
}),
|
|
24
|
-
timestamp: t.String()
|
|
25
|
-
}, {
|
|
26
|
-
description: 'Static files configuration and paths information'
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
export interface StaticFilesConfig {
|
|
30
|
-
publicDir?: string // Default: 'public'
|
|
31
|
-
uploadsDir?: string // Default: 'uploads'
|
|
32
|
-
cacheMaxAge?: number // Default: 1 year in seconds
|
|
33
|
-
enableUploads?: boolean // Default: true
|
|
34
|
-
enablePublic?: boolean // Default: true
|
|
35
|
-
publicRoute?: string // Default: '/public' (can be '/static' in dev)
|
|
36
|
-
uploadsRoute?: string // Default: '/uploads'
|
|
37
|
-
}
|
|
4
|
+
import { mkdir } from 'fs/promises'
|
|
5
|
+
import { resolve } from 'path'
|
|
6
|
+
import type { Plugin, PluginContext } from '../../plugins/types'
|
|
38
7
|
|
|
39
8
|
export const staticFilesPlugin: Plugin = {
|
|
40
9
|
name: 'static-files',
|
|
41
|
-
description: 'Serve static files and uploads
|
|
10
|
+
description: 'Serve static files and uploads',
|
|
42
11
|
author: 'FluxStack Team',
|
|
43
12
|
priority: 'normal',
|
|
44
13
|
category: 'core',
|
|
45
|
-
tags: ['static', 'files', 'uploads'
|
|
46
|
-
|
|
14
|
+
tags: ['static', 'files', 'uploads'],
|
|
15
|
+
|
|
47
16
|
setup: async (context: PluginContext) => {
|
|
48
|
-
context.logger.debug('📁 Setting up Static Files plugin...')
|
|
49
|
-
|
|
50
|
-
const config: StaticFilesConfig = {
|
|
51
|
-
publicDir: 'public',
|
|
52
|
-
uploadsDir: 'uploads',
|
|
53
|
-
cacheMaxAge: 31536000, // 1 year
|
|
54
|
-
enableUploads: true,
|
|
55
|
-
enablePublic: true,
|
|
56
|
-
publicRoute: '/api/static', // Use /api/static in dev to avoid Vite conflicts
|
|
57
|
-
uploadsRoute: '/api/uploads',
|
|
58
|
-
...context.config.staticFiles
|
|
59
|
-
}
|
|
60
|
-
|
|
61
17
|
const projectRoot = process.cwd()
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
'.txt': 'text/plain',
|
|
80
|
-
'.json': 'application/json',
|
|
81
|
-
'.xml': 'application/xml',
|
|
82
|
-
|
|
83
|
-
// Web assets
|
|
84
|
-
'.css': 'text/css',
|
|
85
|
-
'.js': 'application/javascript',
|
|
86
|
-
'.html': 'text/html',
|
|
87
|
-
'.htm': 'text/html',
|
|
88
|
-
|
|
89
|
-
// Fonts
|
|
90
|
-
'.woff': 'font/woff',
|
|
91
|
-
'.woff2': 'font/woff2',
|
|
92
|
-
'.ttf': 'font/ttf',
|
|
93
|
-
'.otf': 'font/otf',
|
|
94
|
-
|
|
95
|
-
// Audio/Video
|
|
96
|
-
'.mp3': 'audio/mpeg',
|
|
97
|
-
'.mp4': 'video/mp4',
|
|
98
|
-
'.webm': 'video/webm',
|
|
99
|
-
'.ogg': 'audio/ogg'
|
|
18
|
+
const publicDir = resolve(projectRoot, 'public')
|
|
19
|
+
const uploadsDir = resolve(projectRoot, 'uploads')
|
|
20
|
+
|
|
21
|
+
// Create directories if they don't exist
|
|
22
|
+
await mkdir(publicDir, { recursive: true })
|
|
23
|
+
await mkdir(uploadsDir, { recursive: true })
|
|
24
|
+
await mkdir(resolve(uploadsDir, 'avatars'), { recursive: true })
|
|
25
|
+
|
|
26
|
+
// Helper to serve files from a directory
|
|
27
|
+
const serveFile = (baseDir: string) => ({ params, set }: any) => {
|
|
28
|
+
const requestedPath = params['*'] || ''
|
|
29
|
+
const filePath = resolve(baseDir, requestedPath)
|
|
30
|
+
|
|
31
|
+
// Path traversal protection
|
|
32
|
+
if (!filePath.startsWith(baseDir)) {
|
|
33
|
+
set.status = 400
|
|
34
|
+
return { error: 'Invalid path' }
|
|
100
35
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Generic file serving function
|
|
112
|
-
const serveFile = async (filePath: string, set: any) => {
|
|
36
|
+
|
|
37
|
+
// Check if file exists
|
|
38
|
+
if (!existsSync(filePath)) {
|
|
39
|
+
set.status = 404
|
|
40
|
+
return { error: 'File not found' }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Check if it's a file (not directory)
|
|
113
44
|
try {
|
|
114
|
-
if (!
|
|
115
|
-
set.status = 404
|
|
116
|
-
return {
|
|
117
|
-
error: 'File not found',
|
|
118
|
-
path: filePath.replace(projectRoot, ''),
|
|
119
|
-
timestamp: new Date().toISOString()
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const stats = statSync(filePath)
|
|
124
|
-
if (!stats.isFile()) {
|
|
45
|
+
if (!statSync(filePath).isFile()) {
|
|
125
46
|
set.status = 404
|
|
126
47
|
return { error: 'Not a file' }
|
|
127
48
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const mimeType = getMimeType(extension)
|
|
132
|
-
|
|
133
|
-
set.headers['content-type'] = mimeType
|
|
134
|
-
set.headers['content-length'] = stats.size.toString()
|
|
135
|
-
set.headers['last-modified'] = stats.mtime.toUTCString()
|
|
136
|
-
set.headers['cache-control'] = `public, max-age=${config.cacheMaxAge}`
|
|
137
|
-
set.headers['etag'] = `"${stats.mtime.getTime()}-${stats.size}"`
|
|
138
|
-
|
|
139
|
-
// Security headers for images
|
|
140
|
-
if (mimeType.startsWith('image/')) {
|
|
141
|
-
set.headers['x-content-type-options'] = 'nosniff'
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
context.logger.debug(`📁 Serving file: ${filePath.replace(projectRoot, '')}`, {
|
|
145
|
-
size: stats.size,
|
|
146
|
-
mimeType,
|
|
147
|
-
lastModified: stats.mtime
|
|
148
|
-
})
|
|
149
|
-
|
|
150
|
-
return Bun.file(filePath)
|
|
151
|
-
|
|
152
|
-
} catch (error: any) {
|
|
153
|
-
context.logger.error('❌ File serving error:', error.message)
|
|
154
|
-
set.status = 500
|
|
155
|
-
return { error: 'Failed to serve file' }
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Add static file routes
|
|
160
|
-
if (config.enablePublic) {
|
|
161
|
-
const publicRoutePattern = `${config.publicRoute}/*`
|
|
162
|
-
context.app.get(publicRoutePattern, ({ params, set }) => {
|
|
163
|
-
const filePath = params['*'] || ''
|
|
164
|
-
|
|
165
|
-
if (!isPathSafe(filePath, publicPath)) {
|
|
166
|
-
set.status = 400
|
|
167
|
-
return { error: 'Invalid file path' }
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const fullPath = join(publicPath, filePath)
|
|
171
|
-
return serveFile(fullPath, set)
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
context.logger.debug(`📁 Public files route enabled: ${publicRoutePattern} → ${config.publicDir}`)
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (config.enableUploads) {
|
|
178
|
-
const uploadsRoutePattern = `${config.uploadsRoute}/*`
|
|
179
|
-
context.app.get(uploadsRoutePattern, ({ params, set }) => {
|
|
180
|
-
const filePath = params['*'] || ''
|
|
181
|
-
|
|
182
|
-
if (!isPathSafe(filePath, uploadsPath)) {
|
|
183
|
-
set.status = 400
|
|
184
|
-
return { error: 'Invalid file path' }
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const fullPath = join(uploadsPath, filePath)
|
|
188
|
-
return serveFile(fullPath, set)
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
context.logger.debug(`📁 Uploads route enabled: ${uploadsRoutePattern} → ${config.uploadsDir}`)
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Static files info endpoint
|
|
195
|
-
context.app.get('/api/static/info', () => {
|
|
196
|
-
return {
|
|
197
|
-
success: true,
|
|
198
|
-
config: {
|
|
199
|
-
publicDir: config.publicDir,
|
|
200
|
-
uploadsDir: config.uploadsDir,
|
|
201
|
-
enablePublic: config.enablePublic,
|
|
202
|
-
enableUploads: config.enableUploads,
|
|
203
|
-
cacheMaxAge: config.cacheMaxAge
|
|
204
|
-
},
|
|
205
|
-
paths: {
|
|
206
|
-
publicPath,
|
|
207
|
-
uploadsPath,
|
|
208
|
-
publicUrl: config.publicRoute,
|
|
209
|
-
uploadsUrl: config.uploadsRoute
|
|
210
|
-
},
|
|
211
|
-
timestamp: new Date().toISOString()
|
|
49
|
+
} catch {
|
|
50
|
+
set.status = 404
|
|
51
|
+
return { error: 'File not found' }
|
|
212
52
|
}
|
|
213
|
-
}, {
|
|
214
|
-
detail: {
|
|
215
|
-
summary: 'Static Files Configuration',
|
|
216
|
-
description: 'Returns configuration and paths for static files and uploads serving',
|
|
217
|
-
tags: ['Static Files', 'Configuration']
|
|
218
|
-
},
|
|
219
|
-
response: StaticFilesInfoSchema
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
// Create directories if they don't exist
|
|
223
|
-
const { mkdir } = await import('fs/promises')
|
|
224
|
-
|
|
225
|
-
if (config.enablePublic && !existsSync(publicPath)) {
|
|
226
|
-
await mkdir(publicPath, { recursive: true })
|
|
227
|
-
context.logger.debug(`📁 Created public directory: ${publicPath}`)
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if (config.enableUploads && !existsSync(uploadsPath)) {
|
|
231
|
-
await mkdir(uploadsPath, { recursive: true })
|
|
232
|
-
await mkdir(join(uploadsPath, 'avatars'), { recursive: true })
|
|
233
|
-
context.logger.debug(`📁 Created uploads directory: ${uploadsPath}`)
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
context.logger.debug('📁 Static Files plugin setup complete', {
|
|
237
|
-
publicEnabled: config.enablePublic,
|
|
238
|
-
uploadsEnabled: config.enableUploads,
|
|
239
|
-
publicPath: config.enablePublic ? publicPath : 'disabled',
|
|
240
|
-
uploadsPath: config.enableUploads ? uploadsPath : 'disabled'
|
|
241
|
-
})
|
|
242
|
-
},
|
|
243
53
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
uploadsRoute: '/api/uploads',
|
|
250
|
-
...context.config.staticFiles
|
|
54
|
+
// Set cache header (1 year)
|
|
55
|
+
set.headers['cache-control'] = 'public, max-age=31536000'
|
|
56
|
+
|
|
57
|
+
// Bun.file() handles: content-type, content-length, streaming
|
|
58
|
+
return Bun.file(filePath)
|
|
251
59
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
60
|
+
|
|
61
|
+
// Register routes
|
|
62
|
+
context.app.get('/api/static/*', serveFile(publicDir))
|
|
63
|
+
context.app.get('/api/uploads/*', serveFile(uploadsDir))
|
|
64
|
+
|
|
65
|
+
context.logger.debug('📁 Static files plugin ready', {
|
|
66
|
+
routes: ['/api/static/*', '/api/uploads/*']
|
|
258
67
|
})
|
|
259
68
|
}
|
|
260
|
-
}
|
|
69
|
+
}
|
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
import { swagger } from '@elysiajs/swagger'
|
|
2
|
-
import type { Plugin, PluginContext } from '@/core/plugins/types'
|
|
3
|
-
|
|
4
|
-
export const swaggerPlugin: Plugin = {
|
|
5
|
-
name: 'swagger',
|
|
6
|
-
setup(context: PluginContext) {
|
|
7
|
-
context.app.use(swagger({
|
|
8
|
-
path: '/swagger',
|
|
9
|
-
documentation: {
|
|
10
|
-
info: {
|
|
11
|
-
title: 'FluxStack API',
|
|
12
|
-
version: '1.7.4',
|
|
13
|
-
description: 'Modern full-stack TypeScript framework with type-safe API endpoints'
|
|
14
|
-
},
|
|
15
|
-
tags: [
|
|
16
|
-
{
|
|
17
|
-
name: 'Health',
|
|
18
|
-
description: 'Health check endpoints'
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
name: 'Users',
|
|
22
|
-
description: 'User management endpoints'
|
|
23
|
-
}
|
|
24
|
-
],
|
|
25
|
-
servers: [
|
|
26
|
-
{
|
|
27
|
-
url: `http://localhost:${context.config.server?.port || 3000}`,
|
|
28
|
-
description: 'Development server'
|
|
29
|
-
}
|
|
30
|
-
]
|
|
31
|
-
}
|
|
32
|
-
}))
|
|
33
|
-
}
|
|
1
|
+
import { swagger } from '@elysiajs/swagger'
|
|
2
|
+
import type { Plugin, PluginContext } from '@/core/plugins/types'
|
|
3
|
+
|
|
4
|
+
export const swaggerPlugin: Plugin = {
|
|
5
|
+
name: 'swagger',
|
|
6
|
+
setup(context: PluginContext) {
|
|
7
|
+
context.app.use(swagger({
|
|
8
|
+
path: '/swagger',
|
|
9
|
+
documentation: {
|
|
10
|
+
info: {
|
|
11
|
+
title: 'FluxStack API',
|
|
12
|
+
version: '1.7.4',
|
|
13
|
+
description: 'Modern full-stack TypeScript framework with type-safe API endpoints'
|
|
14
|
+
},
|
|
15
|
+
tags: [
|
|
16
|
+
{
|
|
17
|
+
name: 'Health',
|
|
18
|
+
description: 'Health check endpoints'
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'Users',
|
|
22
|
+
description: 'User management endpoints'
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
servers: [
|
|
26
|
+
{
|
|
27
|
+
url: `http://localhost:${context.config.server?.port || 3000}`,
|
|
28
|
+
description: 'Development server'
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
}))
|
|
33
|
+
}
|
|
34
34
|
}
|
package/core/types/build.ts
CHANGED
|
@@ -83,6 +83,28 @@ export interface BundleOptions {
|
|
|
83
83
|
external?: string[]
|
|
84
84
|
minify?: boolean
|
|
85
85
|
sourceMaps?: boolean
|
|
86
|
+
target?: string
|
|
87
|
+
executable?: ExecutableOptions
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Options for compiling standalone executables
|
|
92
|
+
* Note: Cross-platform compilation is limited - executables are built for the current platform.
|
|
93
|
+
* To build for different platforms, run the build on that platform.
|
|
94
|
+
*/
|
|
95
|
+
export interface ExecutableOptions {
|
|
96
|
+
// Windows-specific options
|
|
97
|
+
windows?: {
|
|
98
|
+
hideConsole?: boolean
|
|
99
|
+
icon?: string
|
|
100
|
+
title?: string
|
|
101
|
+
publisher?: string
|
|
102
|
+
version?: string
|
|
103
|
+
description?: string
|
|
104
|
+
copyright?: string
|
|
105
|
+
}
|
|
106
|
+
// Additional custom build arguments
|
|
107
|
+
customArgs?: string[]
|
|
86
108
|
}
|
|
87
109
|
|
|
88
110
|
export interface OptimizationOptions {
|
package/core/types/plugin.ts
CHANGED
|
@@ -13,7 +13,15 @@ export type {
|
|
|
13
13
|
PluginUtils,
|
|
14
14
|
RequestContext,
|
|
15
15
|
ResponseContext,
|
|
16
|
-
ErrorContext
|
|
16
|
+
ErrorContext,
|
|
17
|
+
BuildContext,
|
|
18
|
+
ConfigLoadContext,
|
|
19
|
+
RouteContext,
|
|
20
|
+
ValidationContext,
|
|
21
|
+
TransformContext,
|
|
22
|
+
BuildAssetContext,
|
|
23
|
+
BuildErrorContext,
|
|
24
|
+
PluginEventContext
|
|
17
25
|
} from "../plugins/types"
|
|
18
26
|
|
|
19
27
|
// Export Plugin as a standalone type for convenience
|