create-fluxstack 1.7.4 → 1.8.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.
Files changed (45) hide show
  1. package/.dockerignore +82 -0
  2. package/Dockerfile +70 -0
  3. package/app/server/app.ts +20 -5
  4. package/app/server/backend-only.ts +15 -12
  5. package/app/server/index.ts +83 -96
  6. package/app/server/live/FluxStackConfig.ts +5 -5
  7. package/app/server/routes/env-test.ts +59 -0
  8. package/config/app.config.ts +2 -54
  9. package/config/client.config.ts +95 -0
  10. package/config/index.ts +57 -22
  11. package/config/monitoring.config.ts +114 -0
  12. package/config/plugins.config.ts +59 -0
  13. package/config/runtime.config.ts +0 -17
  14. package/config/server.config.ts +50 -30
  15. package/core/build/bundler.ts +17 -16
  16. package/core/build/flux-plugins-generator.ts +29 -18
  17. package/core/build/index.ts +72 -65
  18. package/core/build/live-components-generator.ts +29 -18
  19. package/core/build/optimizer.ts +37 -17
  20. package/core/cli/index.ts +6 -2
  21. package/core/config/env.ts +4 -0
  22. package/core/config/runtime-config.ts +10 -8
  23. package/core/config/schema.ts +24 -2
  24. package/core/framework/server.ts +1 -0
  25. package/core/index.ts +31 -23
  26. package/core/plugins/built-in/monitoring/index.ts +2 -0
  27. package/core/plugins/built-in/static/index.ts +73 -244
  28. package/core/plugins/built-in/swagger/index.ts +2 -0
  29. package/core/plugins/built-in/vite/index.ts +377 -374
  30. package/core/plugins/config.ts +2 -0
  31. package/core/plugins/discovery.ts +2 -0
  32. package/core/plugins/executor.ts +2 -0
  33. package/core/plugins/index.ts +2 -2
  34. package/core/plugins/registry.ts +22 -18
  35. package/core/server/backend-entry.ts +51 -0
  36. package/core/types/plugin.ts +6 -0
  37. package/core/utils/build-logger.ts +324 -0
  38. package/core/utils/config-schema.ts +2 -6
  39. package/core/utils/helpers.ts +14 -9
  40. package/core/utils/regenerate-files.ts +69 -0
  41. package/core/utils/version.ts +1 -1
  42. package/fluxstack.config.ts +138 -252
  43. package/package.json +2 -17
  44. package/vitest.config.ts +8 -26
  45. package/config/build.config.ts +0 -24
package/core/index.ts CHANGED
@@ -3,26 +3,34 @@
3
3
  * Main exports for the FluxStack framework
4
4
  */
5
5
 
6
- // Server exports
7
- export * from './server/services/index.js'
8
- export * from './server/middleware/index.js'
9
-
10
- // Client exports
11
- export * from './client/index.js'
12
- export * from './client/state/index.js'
13
- export * from './client/hooks/index.js'
14
-
15
- // Testing exports
16
- export * from './testing/index.js'
17
-
18
- // Existing exports
19
- export * from './build/index.js'
20
- export * from './cli/generators/index.js'
21
- export * from './config/index.js'
22
- export * from './framework/server.js'
23
- export * from './framework/client.js'
24
- export * from './plugins/index.js'
25
- export * from './utils/logger/index.js'
26
- export * from './utils/errors/index.js'// Ve
27
- rsion exports
28
- export { FLUXSTACK_VERSION, getVersion, getVersionInfo } from './utils/version.js'
6
+ // Framework core (primary exports)
7
+ export { FluxStackFramework } from './framework/server'
8
+ export { FluxStackClient } from './framework/client'
9
+
10
+ // Server components (includes config types via re-export)
11
+ export * from './server'
12
+
13
+ // Client components
14
+ export * from './client'
15
+
16
+ // Testing utilities
17
+ export * from './testing'
18
+
19
+ // Build system
20
+ export * from './build'
21
+
22
+ // CLI and generators
23
+ export * from './cli/generators'
24
+
25
+ // Plugin system (avoid wildcard to prevent conflicts)
26
+ export { PluginRegistry } from './plugins/registry'
27
+ export { PluginDiscovery, pluginDiscovery } from './plugins/discovery'
28
+ export { PluginManager } from './plugins/manager'
29
+ export { PluginUtils } from './plugins'
30
+
31
+ // Utilities
32
+ export * from './utils/logger'
33
+ // Note: errors are already exported via ./server (BuildError), avoid duplicate export
34
+
35
+ // Version
36
+ export { FLUXSTACK_VERSION } from './utils/version'
@@ -61,6 +61,8 @@ export interface AlertThreshold {
61
61
  message?: string
62
62
  }
63
63
 
64
+ type Plugin = FluxStack.Plugin
65
+
64
66
  export const monitoringPlugin: Plugin = {
65
67
  name: "monitoring",
66
68
  version: "1.0.0",
@@ -1,17 +1,17 @@
1
- import { join, extname } from "path"
2
- import { existsSync, statSync } from "fs"
3
- import type { FluxStack, PluginContext } from "../../types"
1
+ import { join } from "path"
2
+ import { statSync, existsSync } from "fs"
3
+ import type { Plugin, PluginContext } from "../.."
4
4
 
5
5
  export const staticPlugin: Plugin = {
6
6
  name: "static",
7
- version: "1.0.0",
8
- description: "Enhanced static file serving plugin for FluxStack with caching and compression",
7
+ version: "2.0.0",
8
+ description: "Simple and efficient static file serving plugin for FluxStack",
9
9
  author: "FluxStack Team",
10
- priority: 100, // Should run after other plugins
10
+ priority: 200, // Run after all other plugins
11
11
  category: "core",
12
12
  tags: ["static", "files", "spa"],
13
- dependencies: [], // No hard dependencies, but works with vite plugin
14
-
13
+ dependencies: [],
14
+
15
15
  configSchema: {
16
16
  type: "object",
17
17
  properties: {
@@ -21,135 +21,102 @@ export const staticPlugin: Plugin = {
21
21
  },
22
22
  publicDir: {
23
23
  type: "string",
24
- description: "Public directory for static files"
25
- },
26
- distDir: {
27
- type: "string",
28
- description: "Distribution directory for built files"
24
+ description: "Directory for static files"
29
25
  },
30
26
  indexFile: {
31
27
  type: "string",
32
28
  description: "Index file for SPA routing"
33
- },
34
- cacheControl: {
35
- type: "object",
36
- properties: {
37
- enabled: { type: "boolean" },
38
- maxAge: { type: "number" },
39
- immutable: { type: "boolean" }
40
- },
41
- description: "Cache control settings"
42
- },
43
- compression: {
44
- type: "object",
45
- properties: {
46
- enabled: { type: "boolean" },
47
- types: {
48
- type: "array",
49
- items: { type: "string" }
50
- }
51
- },
52
- description: "Compression settings"
53
- },
54
- spa: {
55
- type: "object",
56
- properties: {
57
- enabled: { type: "boolean" },
58
- fallback: { type: "string" }
59
- },
60
- description: "Single Page Application settings"
61
- },
62
- excludePaths: {
63
- type: "array",
64
- items: { type: "string" },
65
- description: "Paths to exclude from static serving"
66
29
  }
67
30
  },
68
31
  additionalProperties: false
69
32
  },
70
-
33
+
71
34
  defaultConfig: {
72
35
  enabled: true,
73
- publicDir: "public",
74
- distDir: "dist/client",
75
- indexFile: "index.html",
76
- cacheControl: {
77
- enabled: true,
78
- maxAge: 31536000, // 1 year for assets
79
- immutable: true
80
- },
81
- compression: {
82
- enabled: true,
83
- types: [".js", ".css", ".html", ".json", ".svg"]
84
- },
85
- spa: {
86
- enabled: true,
87
- fallback: "index.html"
88
- },
89
- excludePaths: []
36
+ publicDir: "./dist/client",
37
+ indexFile: "index.html"
90
38
  },
91
39
 
92
40
  setup: async (context: PluginContext) => {
93
41
  const config = getPluginConfig(context)
94
-
42
+
95
43
  if (!config.enabled) {
96
- context.logger.info('Static files plugin disabled by configuration')
44
+ context.logger.info('Static files plugin disabled')
97
45
  return
98
46
  }
99
47
 
100
- context.logger.info("Enhanced static files plugin activated", {
101
- publicDir: config.publicDir,
102
- distDir: config.distDir,
103
- spa: config.spa.enabled,
104
- compression: config.compression.enabled
48
+ context.logger.info("Static files plugin activated", {
49
+ publicDir: config.publicDir
105
50
  })
106
-
107
- // Helper function for handling both GET and HEAD requests
108
- const handleStaticRequest = async ({ request, set }: { request: Request, set: any }) => {
109
- const url = new URL(request.url)
110
-
111
- // Skip API routes
112
- if (url.pathname.startsWith(context.config.server.apiPrefix)) {
113
- return
51
+
52
+ // Static fallback handler (runs last)
53
+ const staticFallback = (c: any) => {
54
+ const req = c.request
55
+ if (!req) return
56
+
57
+ const url = new URL(req.url)
58
+ let pathname = decodeURIComponent(url.pathname)
59
+
60
+ // Determine base directory using path discovery
61
+ const isDev = context.utils.isDevelopment()
62
+ let baseDir: string
63
+
64
+ if (isDev && existsSync(config.publicDir)) {
65
+ // Development: use public directory
66
+ baseDir = config.publicDir
67
+ } else {
68
+ // Production: try paths in order of preference
69
+ if (existsSync('client')) {
70
+ // Found client/ in current directory (running from dist/)
71
+ baseDir = 'client'
72
+ } else if (existsSync('dist/client')) {
73
+ // Found dist/client/ (running from project root)
74
+ baseDir = 'dist/client'
75
+ } else {
76
+ // Fallback to configured path
77
+ baseDir = config.publicDir
78
+ }
114
79
  }
115
-
116
- // Skip excluded paths
117
- if (config.excludePaths.some((path: string) => url.pathname.startsWith(path))) {
118
- return
80
+
81
+ // Root or empty path → index.html
82
+ if (pathname === '/' || pathname === '') {
83
+ pathname = `/${config.indexFile}`
84
+ }
85
+
86
+ const filePath = join(baseDir, pathname)
87
+
88
+ try {
89
+ const info = statSync(filePath)
90
+
91
+ // File exists → serve it
92
+ if (info.isFile()) {
93
+ return new Response(Bun.file(filePath))
94
+ }
95
+ } catch (_) {
96
+ // File not found → continue
119
97
  }
120
-
98
+
99
+ // SPA fallback: serve index.html for non-file routes
100
+ const indexPath = join(baseDir, config.indexFile)
121
101
  try {
122
- // Note: Vite proxy is now handled by the Vite plugin via onBeforeRoute hook
123
- // This plugin only handles static files serving in production or fallback
124
-
125
- // Serve static files
126
- return await serveStaticFile(url.pathname, config, context, set, request.method === 'HEAD')
127
-
128
- } catch (error) {
129
- context.logger.error("Error serving static file", {
130
- path: url.pathname,
131
- error: error instanceof Error ? error.message : String(error)
132
- })
133
-
134
- set.status = 500
135
- return "Internal Server Error"
102
+ statSync(indexPath) // Ensure index exists
103
+ return new Response(Bun.file(indexPath))
104
+ } catch (_) {
105
+ // Index not found → let request continue (404)
136
106
  }
137
107
  }
138
108
 
139
- // Setup static file handling in Elysia - handle both GET and HEAD
140
- context.app.get("/*", handleStaticRequest)
141
- context.app.head("/*", handleStaticRequest)
109
+ // Register as catch-all fallback (runs after all other routes)
110
+ context.app.all('*', staticFallback)
142
111
  },
143
112
 
144
113
  onServerStart: async (context: PluginContext) => {
145
114
  const config = getPluginConfig(context)
146
-
115
+
147
116
  if (config.enabled) {
148
- const mode = context.utils.isDevelopment() ? 'development' : 'production'
149
- context.logger.info(`Static files plugin ready in ${mode} mode`, {
117
+ context.logger.info(`Static files plugin ready`, {
150
118
  publicDir: config.publicDir,
151
- distDir: config.distDir,
152
- spa: config.spa.enabled
119
+ indexFile: config.indexFile
153
120
  })
154
121
  }
155
122
  }
@@ -161,143 +128,5 @@ function getPluginConfig(context: PluginContext) {
161
128
  return { ...staticPlugin.defaultConfig, ...pluginConfig }
162
129
  }
163
130
 
164
- // Serve static file
165
- async function serveStaticFile(
166
- pathname: string,
167
- config: any,
168
- context: PluginContext,
169
- set: any,
170
- isHead: boolean = false
171
- ): Promise<any> {
172
- const isDev = context.utils.isDevelopment()
173
-
174
- // Determine base directory using path discovery (no hardcoded detection)
175
- let baseDir: string
176
-
177
- if (isDev && existsSync(config.publicDir)) {
178
- // Development: use public directory
179
- baseDir = config.publicDir
180
- } else {
181
- // Production: try paths in order of preference
182
- if (existsSync('client')) {
183
- // Found client/ in current directory (running from dist/)
184
- baseDir = 'client'
185
- } else if (existsSync('dist/client')) {
186
- // Found dist/client/ (running from project root)
187
- baseDir = 'dist/client'
188
- } else {
189
- // Fallback to configured path
190
- baseDir = config.distDir
191
- }
192
- }
193
-
194
- if (!existsSync(baseDir)) {
195
- context.logger.warn(`Static directory not found: ${baseDir}`)
196
- set.status = 404
197
- return "Not Found"
198
- }
199
-
200
- // Clean pathname
201
- const cleanPath = pathname === '/' ? `/${config.indexFile}` : pathname
202
- const filePath = join(process.cwd(), baseDir, cleanPath)
203
-
204
- // Security check - prevent directory traversal
205
- const resolvedPath = join(process.cwd(), baseDir)
206
- if (!filePath.startsWith(resolvedPath)) {
207
- set.status = 403
208
- return "Forbidden"
209
- }
210
-
211
- // Check if file exists
212
- if (!existsSync(filePath)) {
213
- // For SPA, serve index.html for non-file routes
214
- if (config.spa.enabled && !pathname.includes('.')) {
215
- const indexPath = join(process.cwd(), baseDir, config.spa.fallback)
216
- if (existsSync(indexPath)) {
217
- return serveFile(indexPath, config, set, context, isHead)
218
- }
219
- }
220
-
221
- set.status = 404
222
- return "Not Found"
223
- }
224
-
225
- // Check if it's a directory
226
- const stats = statSync(filePath)
227
- if (stats.isDirectory()) {
228
- const indexPath = join(filePath, config.indexFile)
229
- if (existsSync(indexPath)) {
230
- return serveFile(indexPath, config, set, context, isHead)
231
- }
232
-
233
- set.status = 404
234
- return "Not Found"
235
- }
236
-
237
- return serveFile(filePath, config, set, context, isHead)
238
- }
239
-
240
- // Serve individual file
241
- function serveFile(filePath: string, config: any, set: any, context: PluginContext, isHead: boolean = false) {
242
- const ext = extname(filePath)
243
- const file = Bun.file(filePath)
244
-
245
- // Set content type
246
- const mimeTypes: Record<string, string> = {
247
- '.html': 'text/html',
248
- '.css': 'text/css',
249
- '.js': 'application/javascript',
250
- '.json': 'application/json',
251
- '.png': 'image/png',
252
- '.jpg': 'image/jpeg',
253
- '.jpeg': 'image/jpeg',
254
- '.gif': 'image/gif',
255
- '.svg': 'image/svg+xml',
256
- '.ico': 'image/x-icon',
257
- '.woff': 'font/woff',
258
- '.woff2': 'font/woff2',
259
- '.ttf': 'font/ttf',
260
- '.eot': 'application/vnd.ms-fontobject'
261
- }
262
-
263
- const contentType = mimeTypes[ext] || 'application/octet-stream'
264
- set.headers['Content-Type'] = contentType
265
-
266
- // Set content-length for both GET and HEAD requests
267
- set.headers['Content-Length'] = file.size.toString()
268
-
269
- // Set cache headers
270
- if (config.cacheControl.enabled) {
271
- if (ext === '.html') {
272
- // Don't cache HTML files aggressively
273
- set.headers['Cache-Control'] = 'no-cache'
274
- } else {
275
- // Cache assets aggressively
276
- const maxAge = config.cacheControl.maxAge
277
- const cacheControl = config.cacheControl.immutable
278
- ? `public, max-age=${maxAge}, immutable`
279
- : `public, max-age=${maxAge}`
280
- set.headers['Cache-Control'] = cacheControl
281
- }
282
- }
283
-
284
- // Add compression hint if enabled
285
- if (config.compression.enabled && config.compression.types.includes(ext)) {
286
- set.headers['Vary'] = 'Accept-Encoding'
287
- }
288
-
289
- context.logger.debug(`Serving static file: ${filePath}`, {
290
- contentType,
291
- size: file.size,
292
- method: isHead ? 'HEAD' : 'GET'
293
- })
294
-
295
- // For HEAD requests, return empty body but keep all headers
296
- if (isHead) {
297
- return ""
298
- }
299
-
300
- return file
301
- }
302
131
 
303
132
  export default staticPlugin
@@ -1,6 +1,8 @@
1
1
  import { swagger } from '@elysiajs/swagger'
2
2
  import type { FluxStack, PluginContext } from '../../types'
3
3
 
4
+ type Plugin = FluxStack.Plugin
5
+
4
6
  export const swaggerPlugin: Plugin = {
5
7
  name: 'swagger',
6
8
  version: '1.0.0',