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.
- package/.dockerignore +82 -0
- package/Dockerfile +70 -0
- package/app/server/app.ts +20 -5
- package/app/server/backend-only.ts +15 -12
- package/app/server/index.ts +83 -96
- package/app/server/live/FluxStackConfig.ts +5 -5
- package/app/server/routes/env-test.ts +59 -0
- package/config/app.config.ts +2 -54
- package/config/client.config.ts +95 -0
- package/config/index.ts +57 -22
- package/config/monitoring.config.ts +114 -0
- package/config/plugins.config.ts +59 -0
- package/config/runtime.config.ts +0 -17
- package/config/server.config.ts +50 -30
- package/core/build/bundler.ts +17 -16
- package/core/build/flux-plugins-generator.ts +29 -18
- package/core/build/index.ts +72 -65
- package/core/build/live-components-generator.ts +29 -18
- package/core/build/optimizer.ts +37 -17
- package/core/cli/index.ts +6 -2
- package/core/config/env.ts +4 -0
- package/core/config/runtime-config.ts +10 -8
- package/core/config/schema.ts +24 -2
- package/core/framework/server.ts +1 -0
- package/core/index.ts +31 -23
- package/core/plugins/built-in/monitoring/index.ts +2 -0
- package/core/plugins/built-in/static/index.ts +73 -244
- package/core/plugins/built-in/swagger/index.ts +2 -0
- package/core/plugins/built-in/vite/index.ts +377 -374
- package/core/plugins/config.ts +2 -0
- package/core/plugins/discovery.ts +2 -0
- package/core/plugins/executor.ts +2 -0
- package/core/plugins/index.ts +2 -2
- package/core/plugins/registry.ts +22 -18
- package/core/server/backend-entry.ts +51 -0
- package/core/types/plugin.ts +6 -0
- package/core/utils/build-logger.ts +324 -0
- package/core/utils/config-schema.ts +2 -6
- package/core/utils/helpers.ts +14 -9
- package/core/utils/regenerate-files.ts +69 -0
- package/core/utils/version.ts +1 -1
- package/fluxstack.config.ts +138 -252
- package/package.json +2 -17
- package/vitest.config.ts +8 -26
- 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
|
-
//
|
|
7
|
-
export
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
//
|
|
11
|
-
export * from './
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
export * from './
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
export * from './
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
export
|
|
27
|
-
|
|
28
|
-
export {
|
|
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'
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { join
|
|
2
|
-
import {
|
|
3
|
-
import type {
|
|
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: "
|
|
8
|
-
description: "
|
|
7
|
+
version: "2.0.0",
|
|
8
|
+
description: "Simple and efficient static file serving plugin for FluxStack",
|
|
9
9
|
author: "FluxStack Team",
|
|
10
|
-
priority:
|
|
10
|
+
priority: 200, // Run after all other plugins
|
|
11
11
|
category: "core",
|
|
12
12
|
tags: ["static", "files", "spa"],
|
|
13
|
-
dependencies: [],
|
|
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: "
|
|
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: "
|
|
74
|
-
|
|
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
|
|
44
|
+
context.logger.info('Static files plugin disabled')
|
|
97
45
|
return
|
|
98
46
|
}
|
|
99
47
|
|
|
100
|
-
context.logger.info("
|
|
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
|
-
//
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
//
|
|
117
|
-
if (
|
|
118
|
-
|
|
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
|
-
//
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
//
|
|
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
|
-
//
|
|
140
|
-
context.app.
|
|
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
|
-
|
|
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
|
-
|
|
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
|