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.
Files changed (31) hide show
  1. package/app/server/live/register-components.ts +6 -26
  2. package/core/build/bundler.ts +53 -5
  3. package/core/build/flux-plugins-generator.ts +315 -0
  4. package/core/build/index.ts +1 -3
  5. package/core/build/live-components-generator.ts +231 -0
  6. package/core/build/optimizer.ts +2 -54
  7. package/core/cli/index.ts +2 -1
  8. package/core/config/env.ts +3 -1
  9. package/core/config/schema.ts +0 -3
  10. package/core/framework/server.ts +33 -1
  11. package/core/plugins/built-in/static/index.ts +24 -10
  12. package/core/plugins/built-in/vite/index.ts +92 -56
  13. package/core/plugins/manager.ts +51 -8
  14. package/core/utils/helpers.ts +9 -3
  15. package/fluxstack.config.ts +4 -10
  16. package/package.json +4 -3
  17. package/plugins/crypto-auth/README.md +238 -0
  18. package/plugins/crypto-auth/client/CryptoAuthClient.ts +325 -0
  19. package/plugins/crypto-auth/client/components/AuthProvider.tsx +190 -0
  20. package/plugins/crypto-auth/client/components/LoginButton.tsx +155 -0
  21. package/plugins/crypto-auth/client/components/ProtectedRoute.tsx +109 -0
  22. package/plugins/crypto-auth/client/components/SessionInfo.tsx +242 -0
  23. package/plugins/crypto-auth/client/components/index.ts +15 -0
  24. package/plugins/crypto-auth/client/index.ts +12 -0
  25. package/plugins/crypto-auth/index.ts +230 -0
  26. package/plugins/crypto-auth/package.json +65 -0
  27. package/plugins/crypto-auth/plugin.json +29 -0
  28. package/plugins/crypto-auth/server/AuthMiddleware.ts +237 -0
  29. package/plugins/crypto-auth/server/CryptoAuthService.ts +293 -0
  30. package/plugins/crypto-auth/server/index.ts +9 -0
  31. 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()
@@ -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
- private async minifyAssets(buildPath: string, results: OptimizationResult): Promise<void> {
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(process.cwd() + "/dist/index.js")
388
+ const { join } = await import("path")
389
+ await import(join(process.cwd(), "dist", "index.js"))
389
390
  break
390
391
 
391
392
  case "create":
@@ -24,7 +24,9 @@ export interface ConfigPrecedence {
24
24
  * Get current environment information
25
25
  */
26
26
  export function getEnvironmentInfo(): EnvironmentInfo {
27
- const nodeEnv = process.env.NODE_ENV || 'development'
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,
@@ -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
@@ -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
- // Setup static file handling in Elysia
108
- context.app.get("/*", async ({ request, set }: { request: Request, set: any }) => {
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