create-fluxstack 1.0.22 → 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/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
|
|
package/core/plugins/manager.ts
CHANGED
|
@@ -64,20 +64,35 @@ export class PluginManager extends EventEmitter {
|
|
|
64
64
|
|
|
65
65
|
try {
|
|
66
66
|
// Discover and load plugins
|
|
67
|
+
this.logger.debug('Starting plugin discovery...')
|
|
67
68
|
await this.discoverPlugins()
|
|
69
|
+
this.logger.debug('Plugin discovery completed')
|
|
68
70
|
|
|
69
71
|
// Setup plugin contexts
|
|
72
|
+
this.logger.debug('Setting up plugin contexts...')
|
|
70
73
|
this.setupPluginContexts()
|
|
74
|
+
this.logger.debug('Plugin contexts setup completed')
|
|
71
75
|
|
|
72
76
|
// Execute setup hooks
|
|
77
|
+
this.logger.debug('Executing setup hooks...')
|
|
73
78
|
await this.executeHook('setup')
|
|
79
|
+
this.logger.debug('Setup hooks execution completed')
|
|
74
80
|
|
|
75
81
|
this.initialized = true
|
|
82
|
+
const stats = this.registry.getStats()
|
|
76
83
|
this.logger.info('Plugin manager initialized successfully', {
|
|
77
|
-
totalPlugins:
|
|
84
|
+
totalPlugins: stats.totalPlugins,
|
|
85
|
+
enabledPlugins: stats.enabledPlugins,
|
|
86
|
+
loadOrder: stats.loadOrder
|
|
78
87
|
})
|
|
79
88
|
} catch (error) {
|
|
80
|
-
this.logger.error('Failed to initialize plugin manager', {
|
|
89
|
+
this.logger.error('Failed to initialize plugin manager', {
|
|
90
|
+
error: error instanceof Error ? {
|
|
91
|
+
message: error.message,
|
|
92
|
+
stack: error.stack,
|
|
93
|
+
name: error.name
|
|
94
|
+
} : error
|
|
95
|
+
})
|
|
81
96
|
throw error
|
|
82
97
|
}
|
|
83
98
|
}
|
|
@@ -396,12 +411,40 @@ export class PluginManager extends EventEmitter {
|
|
|
396
411
|
*/
|
|
397
412
|
private async discoverPlugins(): Promise<void> {
|
|
398
413
|
try {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
414
|
+
// Load built-in plugins first (handled by core system)
|
|
415
|
+
this.logger.info('Loading built-in plugins...')
|
|
416
|
+
const builtInResults = await this.registry.discoverPlugins({
|
|
417
|
+
directories: [],
|
|
418
|
+
includeBuiltIn: true,
|
|
419
|
+
includeExternal: false
|
|
404
420
|
})
|
|
421
|
+
|
|
422
|
+
// Try to use auto-generated registry for external plugins (if available from build)
|
|
423
|
+
let externalResults: any[] = []
|
|
424
|
+
try {
|
|
425
|
+
const autoRegistryModule = await import('./auto-registry')
|
|
426
|
+
if (autoRegistryModule.discoveredPlugins && autoRegistryModule.registerDiscoveredPlugins) {
|
|
427
|
+
this.logger.info('🚀 Using auto-generated external plugins registry')
|
|
428
|
+
await autoRegistryModule.registerDiscoveredPlugins(this.registry)
|
|
429
|
+
externalResults = autoRegistryModule.discoveredPlugins.map((plugin: any) => ({
|
|
430
|
+
success: true,
|
|
431
|
+
plugin
|
|
432
|
+
}))
|
|
433
|
+
}
|
|
434
|
+
} catch (error) {
|
|
435
|
+
this.logger.debug('Auto-generated external plugins registry not found, falling back to discovery', { error: error.message })
|
|
436
|
+
|
|
437
|
+
// Fallback to runtime discovery for external plugins
|
|
438
|
+
this.logger.info('Discovering external plugins in directory: plugins')
|
|
439
|
+
externalResults = await this.registry.discoverPlugins({
|
|
440
|
+
directories: ['plugins'],
|
|
441
|
+
includeBuiltIn: false,
|
|
442
|
+
includeExternal: true
|
|
443
|
+
})
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Combine results
|
|
447
|
+
const results = [...builtInResults, ...externalResults]
|
|
405
448
|
|
|
406
449
|
let loaded = 0
|
|
407
450
|
let failed = 0
|
|
@@ -448,7 +491,7 @@ export class PluginManager extends EventEmitter {
|
|
|
448
491
|
|
|
449
492
|
const context: PluginContext = {
|
|
450
493
|
config: this.config,
|
|
451
|
-
logger: this.logger,
|
|
494
|
+
logger: this.logger.child ? this.logger.child({ plugin: plugin.name }) : this.logger,
|
|
452
495
|
app: this.app,
|
|
453
496
|
utils: createPluginUtils(this.logger),
|
|
454
497
|
registry: this.registry
|
package/core/utils/helpers.ts
CHANGED
|
@@ -87,15 +87,21 @@ export const throttle = <T extends (...args: any[]) => any>(
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
export const isProduction = (): boolean => {
|
|
90
|
-
|
|
90
|
+
// Import here to avoid circular dependency
|
|
91
|
+
const { env } = require('./env-runtime-v2')
|
|
92
|
+
return env.NODE_ENV === 'production'
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
export const isDevelopment = (): boolean => {
|
|
94
|
-
|
|
96
|
+
// Import here to avoid circular dependency
|
|
97
|
+
const { env } = require('./env-runtime-v2')
|
|
98
|
+
return env.NODE_ENV === 'development' || !env.NODE_ENV
|
|
95
99
|
}
|
|
96
100
|
|
|
97
101
|
export const isTest = (): boolean => {
|
|
98
|
-
|
|
102
|
+
// Import here to avoid circular dependency
|
|
103
|
+
const { env } = require('./env-runtime-v2')
|
|
104
|
+
return env.NODE_ENV === 'test'
|
|
99
105
|
}
|
|
100
106
|
|
|
101
107
|
export const deepMerge = <T extends Record<string, any>>(target: T, source: Partial<T>): T => {
|
package/fluxstack.config.ts
CHANGED
|
@@ -42,7 +42,6 @@ export const config: FluxStackConfig = {
|
|
|
42
42
|
},
|
|
43
43
|
build: {
|
|
44
44
|
sourceMaps: env.get('CLIENT_SOURCEMAPS', helpers.isDevelopment()),
|
|
45
|
-
minify: env.get('CLIENT_MINIFY', helpers.isProduction()),
|
|
46
45
|
target: env.get('CLIENT_TARGET', 'esnext'),
|
|
47
46
|
outDir: env.get('CLIENT_OUTDIR', 'dist/client')
|
|
48
47
|
}
|
|
@@ -53,7 +52,6 @@ export const config: FluxStackConfig = {
|
|
|
53
52
|
target: env.get('BUILD_TARGET', 'bun'), // string casting
|
|
54
53
|
outDir: env.get('BUILD_OUTDIR', 'dist'), // string
|
|
55
54
|
optimization: {
|
|
56
|
-
minify: env.get('BUILD_MINIFY', helpers.isProduction()),
|
|
57
55
|
treeshake: env.get('BUILD_TREESHAKE', helpers.isProduction()),
|
|
58
56
|
compress: env.get('BUILD_COMPRESS', helpers.isProduction()),
|
|
59
57
|
splitChunks: env.get('BUILD_SPLIT_CHUNKS', true),
|
|
@@ -193,7 +191,6 @@ export const config: FluxStackConfig = {
|
|
|
193
191
|
port: 5173,
|
|
194
192
|
proxy: { target: 'http://localhost:3000' },
|
|
195
193
|
build: {
|
|
196
|
-
minify: false,
|
|
197
194
|
sourceMaps: true,
|
|
198
195
|
target: 'es2020',
|
|
199
196
|
outDir: 'dist'
|
|
@@ -203,7 +200,6 @@ export const config: FluxStackConfig = {
|
|
|
203
200
|
target: 'bun',
|
|
204
201
|
outDir: 'dist',
|
|
205
202
|
optimization: {
|
|
206
|
-
minify: false,
|
|
207
203
|
compress: false,
|
|
208
204
|
treeshake: false,
|
|
209
205
|
splitChunks: false,
|
|
@@ -246,7 +242,6 @@ export const config: FluxStackConfig = {
|
|
|
246
242
|
port: 5173,
|
|
247
243
|
proxy: { target: 'http://localhost:3000' },
|
|
248
244
|
build: {
|
|
249
|
-
minify: true,
|
|
250
245
|
sourceMaps: false,
|
|
251
246
|
target: 'es2020',
|
|
252
247
|
outDir: 'dist'
|
|
@@ -256,10 +251,9 @@ export const config: FluxStackConfig = {
|
|
|
256
251
|
target: 'bun',
|
|
257
252
|
outDir: 'dist',
|
|
258
253
|
optimization: {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
splitChunks: true,
|
|
254
|
+
treeshake: false,
|
|
255
|
+
compress: false,
|
|
256
|
+
splitChunks: false,
|
|
263
257
|
bundleAnalyzer: false
|
|
264
258
|
},
|
|
265
259
|
sourceMaps: false,
|
|
@@ -306,7 +300,7 @@ export const config: FluxStackConfig = {
|
|
|
306
300
|
client: {
|
|
307
301
|
port: 0, // Use random available port
|
|
308
302
|
proxy: { target: 'http://localhost:3000' },
|
|
309
|
-
build: { sourceMaps: true,
|
|
303
|
+
build: { sourceMaps: true, target: 'es2020', outDir: 'dist' }
|
|
310
304
|
},
|
|
311
305
|
monitoring: {
|
|
312
306
|
enabled: false,
|