create-fluxstack 1.0.21 → 1.1.0
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/live/register-components.ts +6 -26
- package/core/build/bundler.ts +53 -5
- package/core/build/flux-plugins-generator.ts +315 -0
- package/core/build/index.ts +1 -3
- package/core/build/live-components-generator.ts +231 -0
- package/core/build/optimizer.ts +2 -54
- package/core/cli/index.ts +2 -1
- package/core/config/env.ts +3 -1
- package/core/config/schema.ts +0 -3
- package/core/framework/server.ts +33 -1
- package/core/plugins/built-in/static/index.ts +24 -10
- package/core/plugins/built-in/vite/index.ts +92 -56
- package/core/plugins/manager.ts +51 -8
- package/core/utils/helpers.ts +9 -3
- package/fluxstack.config.ts +4 -10
- package/package.json +4 -3
- package/plugins/crypto-auth/README.md +238 -0
- package/plugins/crypto-auth/client/CryptoAuthClient.ts +325 -0
- package/plugins/crypto-auth/client/components/AuthProvider.tsx +190 -0
- package/plugins/crypto-auth/client/components/LoginButton.tsx +155 -0
- package/plugins/crypto-auth/client/components/ProtectedRoute.tsx +109 -0
- package/plugins/crypto-auth/client/components/SessionInfo.tsx +242 -0
- package/plugins/crypto-auth/client/components/index.ts +15 -0
- package/plugins/crypto-auth/client/index.ts +12 -0
- package/plugins/crypto-auth/index.ts +230 -0
- package/plugins/crypto-auth/package.json +65 -0
- package/plugins/crypto-auth/plugin.json +29 -0
- package/plugins/crypto-auth/server/AuthMiddleware.ts +237 -0
- package/plugins/crypto-auth/server/CryptoAuthService.ts +293 -0
- package/plugins/crypto-auth/server/index.ts +9 -0
- package/vite.config.ts +16 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// 🚀 FluxStack Live Components - Auto Registration Generator
|
|
2
|
+
// Automatically generates component registration during build time
|
|
3
|
+
|
|
4
|
+
import { existsSync, readdirSync, writeFileSync, unlinkSync, readFileSync } from 'fs'
|
|
5
|
+
import { join, extname, basename } from 'path'
|
|
6
|
+
|
|
7
|
+
export interface ComponentInfo {
|
|
8
|
+
fileName: string
|
|
9
|
+
className: string
|
|
10
|
+
componentName: string
|
|
11
|
+
filePath: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class LiveComponentsGenerator {
|
|
15
|
+
private componentsPath: string
|
|
16
|
+
private registrationFilePath: string
|
|
17
|
+
private backupFilePath: string
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
this.componentsPath = join(process.cwd(), 'app', 'server', 'live')
|
|
21
|
+
this.registrationFilePath = join(process.cwd(), 'app', 'server', 'live', 'register-components.ts')
|
|
22
|
+
this.backupFilePath = join(process.cwd(), 'app', 'server', 'live', 'register-components.backup.ts')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Scan the live components directory and discover all components
|
|
27
|
+
*/
|
|
28
|
+
discoverComponents(): ComponentInfo[] {
|
|
29
|
+
if (!existsSync(this.componentsPath)) {
|
|
30
|
+
console.log('⚠️ Live components directory not found:', this.componentsPath)
|
|
31
|
+
return []
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const components: ComponentInfo[] = []
|
|
35
|
+
const files = readdirSync(this.componentsPath)
|
|
36
|
+
|
|
37
|
+
for (const file of files) {
|
|
38
|
+
// Skip non-TypeScript files, backup files, and the registration file itself
|
|
39
|
+
if (!file.endsWith('.ts') ||
|
|
40
|
+
file === 'register-components.ts' ||
|
|
41
|
+
file.includes('.backup.') ||
|
|
42
|
+
file.includes('.bak')) {
|
|
43
|
+
continue
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const filePath = join(this.componentsPath, file)
|
|
47
|
+
const fileName = basename(file, extname(file))
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
// Read file content to extract class name
|
|
51
|
+
const content = readFileSync(filePath, 'utf-8')
|
|
52
|
+
|
|
53
|
+
// Look for class exports that extend LiveComponent
|
|
54
|
+
const classMatches = content.match(/export\s+class\s+(\w+)\s+extends\s+LiveComponent/g)
|
|
55
|
+
|
|
56
|
+
if (classMatches && classMatches.length > 0) {
|
|
57
|
+
for (const match of classMatches) {
|
|
58
|
+
const classNameMatch = match.match(/class\s+(\w+)/)
|
|
59
|
+
if (classNameMatch) {
|
|
60
|
+
const className = classNameMatch[1]
|
|
61
|
+
const componentName = className.replace(/Component$/, '')
|
|
62
|
+
|
|
63
|
+
components.push({
|
|
64
|
+
fileName,
|
|
65
|
+
className,
|
|
66
|
+
componentName,
|
|
67
|
+
filePath: `./${fileName}`
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
console.log(`🔍 Discovered component: ${className} -> ${componentName}`)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.warn(`⚠️ Failed to analyze ${file}:`, error)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return components
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Generate the registration file with all discovered components
|
|
84
|
+
*/
|
|
85
|
+
generateRegistrationFile(components: ComponentInfo[]): void {
|
|
86
|
+
// Backup existing file if it exists
|
|
87
|
+
if (existsSync(this.registrationFilePath)) {
|
|
88
|
+
const existingContent = readFileSync(this.registrationFilePath, 'utf-8')
|
|
89
|
+
writeFileSync(this.backupFilePath, existingContent)
|
|
90
|
+
console.log('📄 Backed up existing register-components.ts')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Generate imports
|
|
94
|
+
const imports = components
|
|
95
|
+
.map(comp => `import { ${comp.className} } from "${comp.filePath}"`)
|
|
96
|
+
.join('\n')
|
|
97
|
+
|
|
98
|
+
// Generate registrations
|
|
99
|
+
const registrations = components
|
|
100
|
+
.map(comp => ` componentRegistry.registerComponentClass('${comp.componentName}', ${comp.className})`)
|
|
101
|
+
.join('\n')
|
|
102
|
+
|
|
103
|
+
// Generate file content
|
|
104
|
+
const fileContent = `// 🔥 Auto-generated Live Components Registration
|
|
105
|
+
// This file is automatically generated during build time - DO NOT EDIT MANUALLY
|
|
106
|
+
// Generated at: ${new Date().toISOString()}
|
|
107
|
+
|
|
108
|
+
${imports}
|
|
109
|
+
import { componentRegistry } from "@/core/server/live/ComponentRegistry"
|
|
110
|
+
|
|
111
|
+
// Register all components statically for production bundle
|
|
112
|
+
function registerAllComponents() {
|
|
113
|
+
try {
|
|
114
|
+
// Auto-generated component registrations
|
|
115
|
+
${registrations}
|
|
116
|
+
|
|
117
|
+
console.log('📝 Live components registered successfully! (${components.length} components)')
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.warn('⚠️ Error registering components:', error)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Auto-register components
|
|
124
|
+
registerAllComponents()
|
|
125
|
+
|
|
126
|
+
// Export all components to ensure they're included in the bundle
|
|
127
|
+
export {
|
|
128
|
+
${components.map(comp => ` ${comp.className}`).join(',\n')}
|
|
129
|
+
}
|
|
130
|
+
`
|
|
131
|
+
|
|
132
|
+
writeFileSync(this.registrationFilePath, fileContent)
|
|
133
|
+
console.log(`✅ Generated registration file with ${components.length} components`)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Restore the original registration file from backup
|
|
138
|
+
*/
|
|
139
|
+
restoreOriginalFile(): void {
|
|
140
|
+
if (existsSync(this.backupFilePath)) {
|
|
141
|
+
const backupContent = readFileSync(this.backupFilePath, 'utf-8')
|
|
142
|
+
writeFileSync(this.registrationFilePath, backupContent)
|
|
143
|
+
unlinkSync(this.backupFilePath)
|
|
144
|
+
console.log('🔄 Restored original register-components.ts')
|
|
145
|
+
} else {
|
|
146
|
+
console.log('⚠️ No backup file found, keeping generated registration file')
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Check if the current registration file is auto-generated
|
|
152
|
+
*/
|
|
153
|
+
isAutoGenerated(): boolean {
|
|
154
|
+
if (!existsSync(this.registrationFilePath)) {
|
|
155
|
+
return false
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const content = readFileSync(this.registrationFilePath, 'utf-8')
|
|
159
|
+
return content.includes('// 🔥 Auto-generated Live Components Registration')
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Pre-build hook: Generate registration file
|
|
164
|
+
*/
|
|
165
|
+
async preBuild(): Promise<ComponentInfo[]> {
|
|
166
|
+
console.log('🚀 [PRE-BUILD] Generating Live Components registration...')
|
|
167
|
+
|
|
168
|
+
const components = this.discoverComponents()
|
|
169
|
+
|
|
170
|
+
if (components.length === 0) {
|
|
171
|
+
console.log('⚠️ No Live Components found to register')
|
|
172
|
+
return []
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
this.generateRegistrationFile(components)
|
|
176
|
+
|
|
177
|
+
return components
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Post-build hook: Clean up generated file (optional)
|
|
182
|
+
*/
|
|
183
|
+
async postBuild(keepGenerated: boolean = false): Promise<void> {
|
|
184
|
+
console.log('🧹 [POST-BUILD] Cleaning up Live Components registration...')
|
|
185
|
+
|
|
186
|
+
if (keepGenerated) {
|
|
187
|
+
console.log('📝 Keeping auto-generated registration file for production')
|
|
188
|
+
// Remove backup since we're keeping the generated version
|
|
189
|
+
if (existsSync(this.backupFilePath)) {
|
|
190
|
+
unlinkSync(this.backupFilePath)
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
this.restoreOriginalFile()
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Development mode: Check if registration needs update
|
|
199
|
+
*/
|
|
200
|
+
needsUpdate(): boolean {
|
|
201
|
+
if (!this.isAutoGenerated()) {
|
|
202
|
+
return false // Manual file, don't touch
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const components = this.discoverComponents()
|
|
206
|
+
const currentContent = readFileSync(this.registrationFilePath, 'utf-8')
|
|
207
|
+
|
|
208
|
+
// Check if all discovered components are in the current file
|
|
209
|
+
for (const comp of components) {
|
|
210
|
+
if (!currentContent.includes(`'${comp.componentName}', ${comp.className}`)) {
|
|
211
|
+
return true
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return false
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Development mode: Update registration if needed
|
|
220
|
+
*/
|
|
221
|
+
updateIfNeeded(): void {
|
|
222
|
+
if (this.needsUpdate()) {
|
|
223
|
+
console.log('🔄 Live Components changed, updating registration...')
|
|
224
|
+
const components = this.discoverComponents()
|
|
225
|
+
this.generateRegistrationFile(components)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Export singleton instance
|
|
231
|
+
export const liveComponentsGenerator = new LiveComponentsGenerator()
|
package/core/build/optimizer.ts
CHANGED
|
@@ -4,7 +4,6 @@ import { gzipSync } from "zlib"
|
|
|
4
4
|
import type { OptimizationConfig, OptimizationResult } from "../types/build"
|
|
5
5
|
|
|
6
6
|
export interface OptimizerConfig {
|
|
7
|
-
minify: boolean
|
|
8
7
|
treeshake: boolean
|
|
9
8
|
compress: boolean
|
|
10
9
|
removeUnusedCSS: boolean
|
|
@@ -36,10 +35,7 @@ export class Optimizer {
|
|
|
36
35
|
// Get original size
|
|
37
36
|
results.originalSize = await this.calculateDirectorySize(buildPath)
|
|
38
37
|
|
|
39
|
-
// Apply optimizations
|
|
40
|
-
if (this.config.minify) {
|
|
41
|
-
await this.minifyAssets(buildPath, results)
|
|
42
|
-
}
|
|
38
|
+
// Apply optimizations (minification removed for compatibility)
|
|
43
39
|
|
|
44
40
|
if (this.config.compress) {
|
|
45
41
|
await this.compressAssets(buildPath, results)
|
|
@@ -80,55 +76,7 @@ export class Optimizer {
|
|
|
80
76
|
}
|
|
81
77
|
}
|
|
82
78
|
|
|
83
|
-
|
|
84
|
-
console.log("🗜️ Minifying assets...")
|
|
85
|
-
|
|
86
|
-
const files = this.getFilesRecursively(buildPath)
|
|
87
|
-
let minifiedCount = 0
|
|
88
|
-
|
|
89
|
-
for (const file of files) {
|
|
90
|
-
const ext = extname(file).toLowerCase()
|
|
91
|
-
|
|
92
|
-
if (ext === '.js' || ext === '.css') {
|
|
93
|
-
try {
|
|
94
|
-
const content = readFileSync(file, 'utf-8')
|
|
95
|
-
const minified = await this.minifyContent(content, ext)
|
|
96
|
-
|
|
97
|
-
if (minified !== content) {
|
|
98
|
-
writeFileSync(file, minified)
|
|
99
|
-
minifiedCount++
|
|
100
|
-
}
|
|
101
|
-
} catch (error) {
|
|
102
|
-
console.warn(`⚠️ Failed to minify ${file}:`, error)
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
results.optimizations.push({
|
|
108
|
-
type: 'minification',
|
|
109
|
-
description: `Minified ${minifiedCount} files`,
|
|
110
|
-
sizeSaved: 0 // Would calculate actual size saved
|
|
111
|
-
})
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
private async minifyContent(content: string, type: string): Promise<string> {
|
|
115
|
-
// Basic minification - in a real implementation, you'd use proper minifiers
|
|
116
|
-
if (type === '.js') {
|
|
117
|
-
return content
|
|
118
|
-
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove comments
|
|
119
|
-
.replace(/\/\/.*$/gm, '') // Remove single-line comments
|
|
120
|
-
.replace(/\s+/g, ' ') // Collapse whitespace
|
|
121
|
-
.trim()
|
|
122
|
-
} else if (type === '.css') {
|
|
123
|
-
return content
|
|
124
|
-
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove comments
|
|
125
|
-
.replace(/\s+/g, ' ') // Collapse whitespace
|
|
126
|
-
.replace(/;\s*}/g, '}') // Remove unnecessary semicolons
|
|
127
|
-
.trim()
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return content
|
|
131
|
-
}
|
|
79
|
+
// Minification methods removed for compatibility with Bun bundler
|
|
132
80
|
|
|
133
81
|
private async compressAssets(buildPath: string, results: OptimizationResult): Promise<void> {
|
|
134
82
|
console.log("📦 Compressing assets...")
|
package/core/cli/index.ts
CHANGED
|
@@ -385,7 +385,8 @@ async function handleLegacyCommands() {
|
|
|
385
385
|
|
|
386
386
|
case "start":
|
|
387
387
|
console.log("🚀 Starting FluxStack production server...")
|
|
388
|
-
await import(
|
|
388
|
+
const { join } = await import("path")
|
|
389
|
+
await import(join(process.cwd(), "dist", "index.js"))
|
|
389
390
|
break
|
|
390
391
|
|
|
391
392
|
case "create":
|
package/core/config/env.ts
CHANGED
|
@@ -24,7 +24,9 @@ export interface ConfigPrecedence {
|
|
|
24
24
|
* Get current environment information
|
|
25
25
|
*/
|
|
26
26
|
export function getEnvironmentInfo(): EnvironmentInfo {
|
|
27
|
-
|
|
27
|
+
// Import here to avoid circular dependency
|
|
28
|
+
const { env } = require('../utils/env-runtime-v2')
|
|
29
|
+
const nodeEnv = env.NODE_ENV
|
|
28
30
|
|
|
29
31
|
return {
|
|
30
32
|
name: nodeEnv,
|
package/core/config/schema.ts
CHANGED
|
@@ -45,7 +45,6 @@ export interface ProxyConfig {
|
|
|
45
45
|
|
|
46
46
|
export interface ClientBuildConfig {
|
|
47
47
|
sourceMaps: boolean
|
|
48
|
-
minify: boolean
|
|
49
48
|
target: string
|
|
50
49
|
outDir: string
|
|
51
50
|
}
|
|
@@ -57,7 +56,6 @@ export interface ClientConfig {
|
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
export interface OptimizationConfig {
|
|
60
|
-
minify: boolean
|
|
61
59
|
treeshake: boolean
|
|
62
60
|
compress: boolean
|
|
63
61
|
splitChunks: boolean
|
|
@@ -70,7 +68,6 @@ export interface BuildConfig {
|
|
|
70
68
|
outDir: string
|
|
71
69
|
optimization: OptimizationConfig
|
|
72
70
|
sourceMaps: boolean
|
|
73
|
-
minify: boolean
|
|
74
71
|
treeshake: boolean
|
|
75
72
|
compress?: boolean
|
|
76
73
|
removeUnusedCSS?: boolean
|
package/core/framework/server.ts
CHANGED
|
@@ -73,7 +73,7 @@ export class FluxStackFramework {
|
|
|
73
73
|
info: (message: string, meta?: any) => logger.info(message, meta),
|
|
74
74
|
warn: (message: string, meta?: any) => logger.warn(message, meta),
|
|
75
75
|
error: (message: string, meta?: any) => logger.error(message, meta),
|
|
76
|
-
child: (context: any) => (logger as any).child(context),
|
|
76
|
+
child: (context: any) => (logger as any).child ? (logger as any).child(context) : logger,
|
|
77
77
|
time: (label: string) => (logger as any).time(label),
|
|
78
78
|
timeEnd: (label: string) => (logger as any).timeEnd(label),
|
|
79
79
|
request: (method: string, path: string, status?: number, duration?: number) =>
|
|
@@ -95,6 +95,7 @@ export class FluxStackFramework {
|
|
|
95
95
|
})
|
|
96
96
|
|
|
97
97
|
this.setupCors()
|
|
98
|
+
this.setupHeadHandler()
|
|
98
99
|
this.setupHooks()
|
|
99
100
|
this.setupErrorHandling()
|
|
100
101
|
|
|
@@ -141,6 +142,37 @@ export class FluxStackFramework {
|
|
|
141
142
|
})
|
|
142
143
|
}
|
|
143
144
|
|
|
145
|
+
private setupHeadHandler() {
|
|
146
|
+
// Global HEAD handler to prevent Elysia's automatic HEAD conversion bug
|
|
147
|
+
this.app.head("*", ({ request, set }) => {
|
|
148
|
+
const url = new URL(request.url)
|
|
149
|
+
|
|
150
|
+
// Handle API routes
|
|
151
|
+
if (url.pathname.startsWith(this.context.config.server.apiPrefix)) {
|
|
152
|
+
set.status = 200
|
|
153
|
+
set.headers['Content-Type'] = 'application/json'
|
|
154
|
+
set.headers['Content-Length'] = '0'
|
|
155
|
+
return ""
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Handle static files (assume they're HTML if no extension)
|
|
159
|
+
const isStatic = url.pathname === '/' || !url.pathname.includes('.')
|
|
160
|
+
if (isStatic) {
|
|
161
|
+
set.status = 200
|
|
162
|
+
set.headers['Content-Type'] = 'text/html'
|
|
163
|
+
set.headers['Content-Length'] = '478' // approximate size of index.html
|
|
164
|
+
set.headers['Cache-Control'] = 'no-cache'
|
|
165
|
+
return ""
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Handle other file types
|
|
169
|
+
set.status = 200
|
|
170
|
+
set.headers['Content-Type'] = 'application/octet-stream'
|
|
171
|
+
set.headers['Content-Length'] = '0'
|
|
172
|
+
return ""
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
|
|
144
176
|
private setupHooks() {
|
|
145
177
|
// Setup onRequest hook and onBeforeRoute hook
|
|
146
178
|
this.app.onRequest(async ({ request, set }) => {
|
|
@@ -104,8 +104,8 @@ export const staticPlugin: Plugin = {
|
|
|
104
104
|
compression: config.compression.enabled
|
|
105
105
|
})
|
|
106
106
|
|
|
107
|
-
//
|
|
108
|
-
|
|
107
|
+
// Helper function for handling both GET and HEAD requests
|
|
108
|
+
const handleStaticRequest = async ({ request, set }: { request: Request, set: any }) => {
|
|
109
109
|
const url = new URL(request.url)
|
|
110
110
|
|
|
111
111
|
// Skip API routes
|
|
@@ -123,7 +123,7 @@ export const staticPlugin: Plugin = {
|
|
|
123
123
|
// This plugin only handles static files serving in production or fallback
|
|
124
124
|
|
|
125
125
|
// Serve static files
|
|
126
|
-
return await serveStaticFile(url.pathname, config, context, set)
|
|
126
|
+
return await serveStaticFile(url.pathname, config, context, set, request.method === 'HEAD')
|
|
127
127
|
|
|
128
128
|
} catch (error) {
|
|
129
129
|
context.logger.error("Error serving static file", {
|
|
@@ -134,7 +134,11 @@ export const staticPlugin: Plugin = {
|
|
|
134
134
|
set.status = 500
|
|
135
135
|
return "Internal Server Error"
|
|
136
136
|
}
|
|
137
|
-
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Setup static file handling in Elysia - handle both GET and HEAD
|
|
140
|
+
context.app.get("/*", handleStaticRequest)
|
|
141
|
+
context.app.head("/*", handleStaticRequest)
|
|
138
142
|
},
|
|
139
143
|
|
|
140
144
|
onServerStart: async (context: PluginContext) => {
|
|
@@ -162,7 +166,8 @@ async function serveStaticFile(
|
|
|
162
166
|
pathname: string,
|
|
163
167
|
config: any,
|
|
164
168
|
context: PluginContext,
|
|
165
|
-
set: any
|
|
169
|
+
set: any,
|
|
170
|
+
isHead: boolean = false
|
|
166
171
|
): Promise<any> {
|
|
167
172
|
const isDev = context.utils.isDevelopment()
|
|
168
173
|
|
|
@@ -209,7 +214,7 @@ async function serveStaticFile(
|
|
|
209
214
|
if (config.spa.enabled && !pathname.includes('.')) {
|
|
210
215
|
const indexPath = join(process.cwd(), baseDir, config.spa.fallback)
|
|
211
216
|
if (existsSync(indexPath)) {
|
|
212
|
-
return serveFile(indexPath, config, set, context)
|
|
217
|
+
return serveFile(indexPath, config, set, context, isHead)
|
|
213
218
|
}
|
|
214
219
|
}
|
|
215
220
|
|
|
@@ -222,18 +227,18 @@ async function serveStaticFile(
|
|
|
222
227
|
if (stats.isDirectory()) {
|
|
223
228
|
const indexPath = join(filePath, config.indexFile)
|
|
224
229
|
if (existsSync(indexPath)) {
|
|
225
|
-
return serveFile(indexPath, config, set, context)
|
|
230
|
+
return serveFile(indexPath, config, set, context, isHead)
|
|
226
231
|
}
|
|
227
232
|
|
|
228
233
|
set.status = 404
|
|
229
234
|
return "Not Found"
|
|
230
235
|
}
|
|
231
236
|
|
|
232
|
-
return serveFile(filePath, config, set, context)
|
|
237
|
+
return serveFile(filePath, config, set, context, isHead)
|
|
233
238
|
}
|
|
234
239
|
|
|
235
240
|
// Serve individual file
|
|
236
|
-
function serveFile(filePath: string, config: any, set: any, context: PluginContext) {
|
|
241
|
+
function serveFile(filePath: string, config: any, set: any, context: PluginContext, isHead: boolean = false) {
|
|
237
242
|
const ext = extname(filePath)
|
|
238
243
|
const file = Bun.file(filePath)
|
|
239
244
|
|
|
@@ -258,6 +263,9 @@ function serveFile(filePath: string, config: any, set: any, context: PluginConte
|
|
|
258
263
|
const contentType = mimeTypes[ext] || 'application/octet-stream'
|
|
259
264
|
set.headers['Content-Type'] = contentType
|
|
260
265
|
|
|
266
|
+
// Set content-length for both GET and HEAD requests
|
|
267
|
+
set.headers['Content-Length'] = file.size.toString()
|
|
268
|
+
|
|
261
269
|
// Set cache headers
|
|
262
270
|
if (config.cacheControl.enabled) {
|
|
263
271
|
if (ext === '.html') {
|
|
@@ -280,9 +288,15 @@ function serveFile(filePath: string, config: any, set: any, context: PluginConte
|
|
|
280
288
|
|
|
281
289
|
context.logger.debug(`Serving static file: ${filePath}`, {
|
|
282
290
|
contentType,
|
|
283
|
-
size: file.size
|
|
291
|
+
size: file.size,
|
|
292
|
+
method: isHead ? 'HEAD' : 'GET'
|
|
284
293
|
})
|
|
285
294
|
|
|
295
|
+
// For HEAD requests, return empty body but keep all headers
|
|
296
|
+
if (isHead) {
|
|
297
|
+
return ""
|
|
298
|
+
}
|
|
299
|
+
|
|
286
300
|
return file
|
|
287
301
|
}
|
|
288
302
|
|