create-fluxstack 1.8.3 โ 1.10.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/LIVE_COMPONENTS_REVIEW.md +781 -0
- package/README.md +653 -275
- package/app/client/src/App.tsx +39 -43
- package/app/client/src/lib/eden-api.ts +2 -7
- package/app/client/src/live/FileUploadExample.tsx +359 -0
- package/app/client/src/live/MinimalLiveClock.tsx +47 -0
- package/app/client/src/live/QuickUploadTest.tsx +193 -0
- package/app/client/src/main.tsx +10 -10
- package/app/client/src/vite-env.d.ts +1 -1
- package/app/client/tsconfig.app.json +45 -44
- package/app/client/tsconfig.node.json +25 -25
- package/app/server/index.ts +30 -103
- package/app/server/live/LiveFileUploadComponent.ts +77 -0
- package/app/server/live/register-components.ts +19 -19
- package/core/build/bundler.ts +202 -55
- package/core/build/index.ts +126 -2
- package/core/build/live-components-generator.ts +68 -1
- package/core/cli/generators/plugin.ts +6 -6
- package/core/cli/index.ts +232 -4
- package/core/client/LiveComponentsProvider.tsx +3 -9
- package/core/client/hooks/AdaptiveChunkSizer.ts +215 -0
- package/core/client/hooks/useChunkedUpload.ts +112 -61
- package/core/client/hooks/useHybridLiveComponent.ts +80 -26
- package/core/client/hooks/useTypedLiveComponent.ts +133 -0
- package/core/client/hooks/useWebSocket.ts +4 -16
- package/core/client/index.ts +20 -2
- package/core/framework/server.ts +181 -8
- package/core/live/ComponentRegistry.ts +5 -1
- package/core/plugins/built-in/index.ts +8 -5
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +55 -63
- package/core/plugins/built-in/vite/index.ts +75 -187
- package/core/plugins/built-in/vite/vite-dev.ts +88 -0
- package/core/plugins/registry.ts +54 -2
- package/core/plugins/types.ts +86 -2
- package/core/server/index.ts +1 -2
- package/core/server/live/ComponentRegistry.ts +14 -5
- package/core/server/live/FileUploadManager.ts +22 -25
- package/core/server/live/auto-generated-components.ts +29 -26
- package/core/server/live/websocket-plugin.ts +19 -5
- package/core/server/plugins/static-files-plugin.ts +49 -240
- package/core/server/plugins/swagger.ts +33 -33
- package/core/types/build.ts +22 -0
- package/core/types/plugin.ts +9 -1
- package/core/types/types.ts +137 -0
- package/core/utils/logger/startup-banner.ts +20 -4
- package/core/utils/version.ts +6 -6
- package/create-fluxstack.ts +7 -7
- package/eslint.config.js +23 -23
- package/package.json +3 -2
- package/plugins/crypto-auth/server/middlewares.ts +19 -19
- package/tsconfig.json +52 -51
- package/workspace.json +5 -5
package/core/build/bundler.ts
CHANGED
|
@@ -79,34 +79,18 @@ export class Bundler {
|
|
|
79
79
|
let liveComponentsGenerator: any = null
|
|
80
80
|
|
|
81
81
|
try {
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const discoveredComponents = await liveComponentsGenerator.preBuild()
|
|
86
|
-
|
|
87
|
-
// ๐ PRE-BUILD: Auto-generate FluxStack Plugins registration
|
|
88
|
-
const pluginsGeneratorModule = await import('./flux-plugins-generator')
|
|
89
|
-
const fluxPluginsGenerator = pluginsGeneratorModule.fluxPluginsGenerator
|
|
90
|
-
const discoveredPlugins = await fluxPluginsGenerator.preBuild()
|
|
91
|
-
|
|
82
|
+
// Run pre-build steps
|
|
83
|
+
liveComponentsGenerator = await this.runPreBuildSteps()
|
|
84
|
+
|
|
92
85
|
// Ensure output directory exists
|
|
93
|
-
|
|
94
|
-
mkdirSync(this.config.outDir, { recursive: true })
|
|
95
|
-
}
|
|
86
|
+
this.ensureOutputDirectory()
|
|
96
87
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
"tailwindcss",
|
|
100
|
-
"lightningcss",
|
|
101
|
-
"vite",
|
|
102
|
-
"@vitejs/plugin-react",
|
|
103
|
-
...(this.config.external || []),
|
|
104
|
-
...(options.external || [])
|
|
105
|
-
]
|
|
88
|
+
// Get external dependencies
|
|
89
|
+
const external = this.getExternalDependencies(options)
|
|
106
90
|
|
|
107
91
|
const buildArgs = [
|
|
108
|
-
"bun", "build",
|
|
109
|
-
entryPoint,
|
|
92
|
+
"bun", "build",
|
|
93
|
+
entryPoint,
|
|
110
94
|
"--outdir", this.config.outDir,
|
|
111
95
|
"--target", this.config.target,
|
|
112
96
|
...external.flatMap(ext => ["--external", ext])
|
|
@@ -132,20 +116,12 @@ export class Bundler {
|
|
|
132
116
|
const exitCode = await buildProcess.exited
|
|
133
117
|
const duration = Date.now() - startTime
|
|
134
118
|
|
|
135
|
-
// ๐งน POST-BUILD: Handle auto-generated registration file
|
|
136
|
-
// (liveComponentsGenerator already available from above)
|
|
137
|
-
|
|
138
119
|
if (exitCode === 0) {
|
|
139
120
|
buildLogger.success(`Server bundle completed in ${buildLogger.formatDuration(duration)}`)
|
|
140
|
-
|
|
141
|
-
//
|
|
142
|
-
await
|
|
143
|
-
|
|
144
|
-
// Cleanup plugins registry
|
|
145
|
-
const pluginsGeneratorModule = await import('./flux-plugins-generator')
|
|
146
|
-
const fluxPluginsGenerator = pluginsGeneratorModule.fluxPluginsGenerator
|
|
147
|
-
await fluxPluginsGenerator.postBuild(false)
|
|
148
|
-
|
|
121
|
+
|
|
122
|
+
// Run post-build cleanup
|
|
123
|
+
await this.runPostBuildCleanup(liveComponentsGenerator)
|
|
124
|
+
|
|
149
125
|
return {
|
|
150
126
|
success: true,
|
|
151
127
|
duration,
|
|
@@ -154,15 +130,10 @@ export class Bundler {
|
|
|
154
130
|
}
|
|
155
131
|
} else {
|
|
156
132
|
buildLogger.error("Server bundle failed")
|
|
157
|
-
|
|
158
|
-
//
|
|
159
|
-
await
|
|
160
|
-
|
|
161
|
-
// Restore plugins registry
|
|
162
|
-
const pluginsGeneratorModule = await import('./flux-plugins-generator')
|
|
163
|
-
const fluxPluginsGenerator = pluginsGeneratorModule.fluxPluginsGenerator
|
|
164
|
-
await fluxPluginsGenerator.postBuild(false)
|
|
165
|
-
|
|
133
|
+
|
|
134
|
+
// Run post-build cleanup
|
|
135
|
+
await this.runPostBuildCleanup(liveComponentsGenerator)
|
|
136
|
+
|
|
166
137
|
const stderr = await new Response(buildProcess.stderr).text()
|
|
167
138
|
return {
|
|
168
139
|
success: false,
|
|
@@ -172,21 +143,142 @@ export class Bundler {
|
|
|
172
143
|
}
|
|
173
144
|
} catch (error) {
|
|
174
145
|
const duration = Date.now() - startTime
|
|
175
|
-
|
|
146
|
+
|
|
147
|
+
// ๐งน CLEANUP: Restore original files on error
|
|
148
|
+
try {
|
|
149
|
+
await this.runPostBuildCleanup(liveComponentsGenerator)
|
|
150
|
+
} catch (cleanupError) {
|
|
151
|
+
buildLogger.warn(`Failed to cleanup generated files: ${cleanupError}`)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
success: false,
|
|
156
|
+
duration,
|
|
157
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async compileToExecutable(entryPoint: string, outputName: string = "app", options: BundleOptions = {}): Promise<BundleResult> {
|
|
163
|
+
buildLogger.section('Executable Build', '๐ฆ')
|
|
164
|
+
|
|
165
|
+
const startTime = Date.now()
|
|
166
|
+
let liveComponentsGenerator: any = null
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
// Run pre-build steps
|
|
170
|
+
liveComponentsGenerator = await this.runPreBuildSteps()
|
|
171
|
+
|
|
172
|
+
// Ensure output directory exists
|
|
173
|
+
this.ensureOutputDirectory()
|
|
174
|
+
|
|
175
|
+
const outputPath = join(this.config.outDir, outputName)
|
|
176
|
+
|
|
177
|
+
// Get external dependencies
|
|
178
|
+
const external = this.getExternalDependencies(options)
|
|
179
|
+
|
|
180
|
+
// Use target from options or fall back to config
|
|
181
|
+
const target = options.target || this.config.target
|
|
182
|
+
|
|
183
|
+
const buildArgs = [
|
|
184
|
+
"bun", "build",
|
|
185
|
+
entryPoint,
|
|
186
|
+
"--compile",
|
|
187
|
+
"--outfile", outputPath,
|
|
188
|
+
"--target", target,
|
|
189
|
+
...external.flatMap(ext => ["--external", ext])
|
|
190
|
+
]
|
|
191
|
+
|
|
192
|
+
if (this.config.sourceMaps) {
|
|
193
|
+
buildArgs.push("--sourcemap")
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (this.config.minify) {
|
|
197
|
+
buildArgs.push("--minify")
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Add Windows-specific options if provided
|
|
201
|
+
if (options.executable?.windows) {
|
|
202
|
+
const winOpts = options.executable.windows
|
|
203
|
+
if (winOpts.hideConsole) {
|
|
204
|
+
buildArgs.push("--windows-hide-console")
|
|
205
|
+
}
|
|
206
|
+
if (winOpts.icon) {
|
|
207
|
+
buildArgs.push("--windows-icon", winOpts.icon)
|
|
208
|
+
}
|
|
209
|
+
if (winOpts.title) {
|
|
210
|
+
buildArgs.push("--windows-title", winOpts.title)
|
|
211
|
+
}
|
|
212
|
+
if (winOpts.publisher) {
|
|
213
|
+
buildArgs.push("--windows-publisher", winOpts.publisher)
|
|
214
|
+
}
|
|
215
|
+
if (winOpts.version) {
|
|
216
|
+
buildArgs.push("--windows-version", winOpts.version)
|
|
217
|
+
}
|
|
218
|
+
if (winOpts.description) {
|
|
219
|
+
buildArgs.push("--windows-description", winOpts.description)
|
|
220
|
+
}
|
|
221
|
+
if (winOpts.copyright) {
|
|
222
|
+
buildArgs.push("--windows-copyright", winOpts.copyright)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Add custom build arguments if provided
|
|
227
|
+
if (options.executable?.customArgs) {
|
|
228
|
+
buildArgs.push(...options.executable.customArgs)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
buildLogger.step(`Compiling ${entryPoint} to ${outputPath}...`)
|
|
232
|
+
|
|
233
|
+
const buildProcess = spawn({
|
|
234
|
+
cmd: buildArgs,
|
|
235
|
+
stdout: "pipe",
|
|
236
|
+
stderr: "pipe",
|
|
237
|
+
env: {
|
|
238
|
+
...process.env,
|
|
239
|
+
NODE_ENV: 'production',
|
|
240
|
+
...options.env
|
|
241
|
+
}
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
const exitCode = await buildProcess.exited
|
|
245
|
+
const duration = Date.now() - startTime
|
|
246
|
+
|
|
247
|
+
if (exitCode === 0) {
|
|
248
|
+
buildLogger.success(`Executable compiled in ${buildLogger.formatDuration(duration)}`)
|
|
249
|
+
|
|
250
|
+
// Run post-build cleanup
|
|
251
|
+
await this.runPostBuildCleanup(liveComponentsGenerator)
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
success: true,
|
|
255
|
+
duration,
|
|
256
|
+
outputPath,
|
|
257
|
+
entryPoint: outputPath
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
buildLogger.error("Executable compilation failed")
|
|
261
|
+
|
|
262
|
+
// Run post-build cleanup
|
|
263
|
+
await this.runPostBuildCleanup(liveComponentsGenerator)
|
|
264
|
+
|
|
265
|
+
const stderr = await new Response(buildProcess.stderr).text()
|
|
266
|
+
return {
|
|
267
|
+
success: false,
|
|
268
|
+
duration,
|
|
269
|
+
error: stderr || "Executable compilation failed"
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
} catch (error) {
|
|
273
|
+
const duration = Date.now() - startTime
|
|
274
|
+
|
|
176
275
|
// ๐งน CLEANUP: Restore original files on error
|
|
177
276
|
try {
|
|
178
|
-
|
|
179
|
-
await liveComponentsGenerator.postBuild(false)
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Cleanup plugins registry
|
|
183
|
-
const pluginsGeneratorModule = await import('./flux-plugins-generator')
|
|
184
|
-
const fluxPluginsGenerator = pluginsGeneratorModule.fluxPluginsGenerator
|
|
185
|
-
await fluxPluginsGenerator.postBuild(false)
|
|
277
|
+
await this.runPostBuildCleanup(liveComponentsGenerator)
|
|
186
278
|
} catch (cleanupError) {
|
|
187
279
|
buildLogger.warn(`Failed to cleanup generated files: ${cleanupError}`)
|
|
188
280
|
}
|
|
189
|
-
|
|
281
|
+
|
|
190
282
|
return {
|
|
191
283
|
success: false,
|
|
192
284
|
duration,
|
|
@@ -195,6 +287,61 @@ export class Bundler {
|
|
|
195
287
|
}
|
|
196
288
|
}
|
|
197
289
|
|
|
290
|
+
/**
|
|
291
|
+
* Get list of external dependencies that should not be bundled
|
|
292
|
+
*/
|
|
293
|
+
private getExternalDependencies(options: BundleOptions = {}): string[] {
|
|
294
|
+
return [
|
|
295
|
+
"@tailwindcss/vite",
|
|
296
|
+
"tailwindcss",
|
|
297
|
+
"lightningcss",
|
|
298
|
+
"vite",
|
|
299
|
+
"@vitejs/plugin-react",
|
|
300
|
+
"rollup",
|
|
301
|
+
...(this.config.external || []),
|
|
302
|
+
...(options.external || [])
|
|
303
|
+
]
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Ensure output directory exists
|
|
308
|
+
*/
|
|
309
|
+
private ensureOutputDirectory(): void {
|
|
310
|
+
if (!existsSync(this.config.outDir)) {
|
|
311
|
+
mkdirSync(this.config.outDir, { recursive: true })
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Run pre-build steps (Live Components and Plugins generation)
|
|
317
|
+
*/
|
|
318
|
+
private async runPreBuildSteps(): Promise<any> {
|
|
319
|
+
// ๐ PRE-BUILD: Auto-generate Live Components registration
|
|
320
|
+
const generatorModule = await import('./live-components-generator')
|
|
321
|
+
const liveComponentsGenerator = generatorModule.liveComponentsGenerator
|
|
322
|
+
await liveComponentsGenerator.preBuild()
|
|
323
|
+
|
|
324
|
+
// ๐ PRE-BUILD: Auto-generate FluxStack Plugins registration
|
|
325
|
+
const pluginsGeneratorModule = await import('./flux-plugins-generator')
|
|
326
|
+
const fluxPluginsGenerator = pluginsGeneratorModule.fluxPluginsGenerator
|
|
327
|
+
await fluxPluginsGenerator.preBuild()
|
|
328
|
+
|
|
329
|
+
return liveComponentsGenerator
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Run post-build cleanup
|
|
334
|
+
*/
|
|
335
|
+
private async runPostBuildCleanup(liveComponentsGenerator: any): Promise<void> {
|
|
336
|
+
if (liveComponentsGenerator) {
|
|
337
|
+
await liveComponentsGenerator.postBuild(false)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const pluginsGeneratorModule = await import('./flux-plugins-generator')
|
|
341
|
+
const fluxPluginsGenerator = pluginsGeneratorModule.fluxPluginsGenerator
|
|
342
|
+
await fluxPluginsGenerator.postBuild(false)
|
|
343
|
+
}
|
|
344
|
+
|
|
198
345
|
private async getClientAssets(): Promise<string[]> {
|
|
199
346
|
// This would analyze the build output to get asset information
|
|
200
347
|
// For now, return empty array - can be enhanced later
|
package/core/build/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { copyFileSync, writeFileSync, existsSync, mkdirSync, readFileSync } from "fs"
|
|
1
|
+
import { copyFileSync, writeFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync } from "fs"
|
|
2
2
|
import { join } from "path"
|
|
3
3
|
import type { FluxStackConfig } from "../config"
|
|
4
4
|
import type { BuildResult, BuildManifest } from "../types/build"
|
|
@@ -6,14 +6,18 @@ import { Bundler } from "./bundler"
|
|
|
6
6
|
import { Optimizer } from "./optimizer"
|
|
7
7
|
import { FLUXSTACK_VERSION } from "../utils/version"
|
|
8
8
|
import { buildLogger } from "../utils/build-logger"
|
|
9
|
+
import type { PluginRegistry } from "../plugins/registry"
|
|
10
|
+
import type { BuildContext, BuildAssetContext, BuildErrorContext } from "../plugins/types"
|
|
9
11
|
|
|
10
12
|
export class FluxStackBuilder {
|
|
11
13
|
private config: FluxStackConfig
|
|
12
14
|
private bundler: Bundler
|
|
13
15
|
private optimizer: Optimizer
|
|
16
|
+
private pluginRegistry?: PluginRegistry
|
|
14
17
|
|
|
15
|
-
constructor(config: FluxStackConfig) {
|
|
18
|
+
constructor(config: FluxStackConfig, pluginRegistry?: PluginRegistry) {
|
|
16
19
|
this.config = config
|
|
20
|
+
this.pluginRegistry = pluginRegistry
|
|
17
21
|
|
|
18
22
|
// Initialize bundler with configuration
|
|
19
23
|
this.bundler = new Bundler({
|
|
@@ -47,6 +51,12 @@ export class FluxStackBuilder {
|
|
|
47
51
|
return await this.bundler.bundleServer("app/server/index.ts")
|
|
48
52
|
}
|
|
49
53
|
|
|
54
|
+
async buildExecutable(outputName?: string, options?: import("../types/build").BundleOptions) {
|
|
55
|
+
// Use app name from config as default, fallback to "FluxStack"
|
|
56
|
+
const name = outputName || this.config.app.name || "FluxStack"
|
|
57
|
+
return await this.bundler.compileToExecutable("app/server/index.ts", name, options)
|
|
58
|
+
}
|
|
59
|
+
|
|
50
60
|
async createDockerFiles() {
|
|
51
61
|
buildLogger.section('Docker Configuration', '๐ณ')
|
|
52
62
|
|
|
@@ -234,7 +244,17 @@ MONITORING_ENABLED=true
|
|
|
234
244
|
|
|
235
245
|
const startTime = Date.now()
|
|
236
246
|
|
|
247
|
+
const buildContext: BuildContext = {
|
|
248
|
+
target: this.config.build.target,
|
|
249
|
+
outDir: this.config.build.outDir,
|
|
250
|
+
mode: (this.config.build.mode || 'production') as 'development' | 'production',
|
|
251
|
+
config: this.config
|
|
252
|
+
}
|
|
253
|
+
|
|
237
254
|
try {
|
|
255
|
+
// Execute onBeforeBuild hooks
|
|
256
|
+
await this.executePluginHooks('onBeforeBuild', buildContext)
|
|
257
|
+
|
|
238
258
|
// Pre-build checks (version sync, etc.)
|
|
239
259
|
await this.runPreBuildChecks()
|
|
240
260
|
|
|
@@ -246,6 +266,9 @@ MONITORING_ENABLED=true
|
|
|
246
266
|
await this.clean()
|
|
247
267
|
}
|
|
248
268
|
|
|
269
|
+
// Execute onBuild hooks
|
|
270
|
+
await this.executePluginHooks('onBuild', buildContext)
|
|
271
|
+
|
|
249
272
|
// Build client and server
|
|
250
273
|
const clientResult = await this.buildClient()
|
|
251
274
|
const serverResult = await this.buildServer()
|
|
@@ -253,6 +276,16 @@ MONITORING_ENABLED=true
|
|
|
253
276
|
// Check if builds were successful
|
|
254
277
|
if (!clientResult.success || !serverResult.success) {
|
|
255
278
|
const errorMessage = clientResult.error || serverResult.error || "Build failed"
|
|
279
|
+
|
|
280
|
+
// Execute onBuildError hooks
|
|
281
|
+
const buildErrorContext: BuildErrorContext = {
|
|
282
|
+
error: new Error(errorMessage),
|
|
283
|
+
file: undefined,
|
|
284
|
+
line: undefined,
|
|
285
|
+
column: undefined
|
|
286
|
+
}
|
|
287
|
+
await this.executePluginHooks('onBuildError', buildErrorContext)
|
|
288
|
+
|
|
256
289
|
return {
|
|
257
290
|
success: false,
|
|
258
291
|
duration: Date.now() - startTime,
|
|
@@ -273,6 +306,9 @@ MONITORING_ENABLED=true
|
|
|
273
306
|
}
|
|
274
307
|
}
|
|
275
308
|
|
|
309
|
+
// Process assets and execute onBuildAsset hooks
|
|
310
|
+
await this.processAssets(this.config.build.outDir)
|
|
311
|
+
|
|
276
312
|
// Optimize build if enabled
|
|
277
313
|
let optimizationResult
|
|
278
314
|
if (this.config.build.optimize) {
|
|
@@ -287,6 +323,9 @@ MONITORING_ENABLED=true
|
|
|
287
323
|
|
|
288
324
|
const duration = Date.now() - startTime
|
|
289
325
|
|
|
326
|
+
// Execute onBuildComplete hooks
|
|
327
|
+
await this.executePluginHooks('onBuildComplete', buildContext)
|
|
328
|
+
|
|
290
329
|
// Print build summary
|
|
291
330
|
buildLogger.summary('Build Completed Successfully', [
|
|
292
331
|
{ label: 'Build Time', value: buildLogger.formatDuration(duration), highlight: true },
|
|
@@ -319,6 +358,15 @@ MONITORING_ENABLED=true
|
|
|
319
358
|
|
|
320
359
|
buildLogger.error(`Build failed: ${errorMessage}`)
|
|
321
360
|
|
|
361
|
+
// Execute onBuildError hooks
|
|
362
|
+
const buildErrorContext: BuildErrorContext = {
|
|
363
|
+
error: error instanceof Error ? error : new Error(errorMessage),
|
|
364
|
+
file: undefined,
|
|
365
|
+
line: undefined,
|
|
366
|
+
column: undefined
|
|
367
|
+
}
|
|
368
|
+
await this.executePluginHooks('onBuildError', buildErrorContext)
|
|
369
|
+
|
|
322
370
|
return {
|
|
323
371
|
success: false,
|
|
324
372
|
duration,
|
|
@@ -409,4 +457,80 @@ MONITORING_ENABLED=true
|
|
|
409
457
|
}
|
|
410
458
|
}
|
|
411
459
|
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Execute plugin hooks for build process
|
|
463
|
+
*/
|
|
464
|
+
private async executePluginHooks(hookName: string, context: any): Promise<void> {
|
|
465
|
+
if (!this.pluginRegistry) return
|
|
466
|
+
|
|
467
|
+
const loadOrder = this.pluginRegistry.getLoadOrder()
|
|
468
|
+
|
|
469
|
+
for (const pluginName of loadOrder) {
|
|
470
|
+
const plugin = this.pluginRegistry.get(pluginName)
|
|
471
|
+
if (!plugin) continue
|
|
472
|
+
|
|
473
|
+
const hookFn = (plugin as any)[hookName]
|
|
474
|
+
if (typeof hookFn === 'function') {
|
|
475
|
+
try {
|
|
476
|
+
await hookFn(context)
|
|
477
|
+
} catch (error) {
|
|
478
|
+
buildLogger.error(`Plugin '${pluginName}' ${hookName} hook failed: ${error instanceof Error ? error.message : String(error)}`)
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Process build assets and execute onBuildAsset hooks
|
|
486
|
+
*/
|
|
487
|
+
private async processAssets(outDir: string): Promise<void> {
|
|
488
|
+
if (!this.pluginRegistry) return
|
|
489
|
+
if (!existsSync(outDir)) return
|
|
490
|
+
|
|
491
|
+
try {
|
|
492
|
+
const processDirectory = async (dir: string) => {
|
|
493
|
+
const entries = readdirSync(dir, { withFileTypes: true })
|
|
494
|
+
|
|
495
|
+
for (const entry of entries) {
|
|
496
|
+
const fullPath = join(dir, entry.name)
|
|
497
|
+
|
|
498
|
+
if (entry.isDirectory()) {
|
|
499
|
+
await processDirectory(fullPath)
|
|
500
|
+
} else if (entry.isFile()) {
|
|
501
|
+
const stat = statSync(fullPath)
|
|
502
|
+
const assetType = this.getAssetType(entry.name)
|
|
503
|
+
|
|
504
|
+
const assetContext: BuildAssetContext = {
|
|
505
|
+
assetPath: fullPath,
|
|
506
|
+
assetType,
|
|
507
|
+
size: stat.size,
|
|
508
|
+
content: undefined // Could read file if plugins need it
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
await this.executePluginHooks('onBuildAsset', assetContext)
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
await processDirectory(outDir)
|
|
517
|
+
} catch (error) {
|
|
518
|
+
buildLogger.warn(`Failed to process assets: ${error instanceof Error ? error.message : String(error)}`)
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Determine asset type from file extension
|
|
524
|
+
*/
|
|
525
|
+
private getAssetType(filename: string): 'js' | 'css' | 'html' | 'image' | 'font' | 'other' {
|
|
526
|
+
const ext = filename.split('.').pop()?.toLowerCase()
|
|
527
|
+
|
|
528
|
+
if (ext === 'js' || ext === 'mjs' || ext === 'cjs') return 'js'
|
|
529
|
+
if (ext === 'css') return 'css'
|
|
530
|
+
if (ext === 'html' || ext === 'htm') return 'html'
|
|
531
|
+
if (ext === 'png' || ext === 'jpg' || ext === 'jpeg' || ext === 'gif' || ext === 'svg' || ext === 'webp') return 'image'
|
|
532
|
+
if (ext === 'woff' || ext === 'woff2' || ext === 'ttf' || ext === 'eot' || ext === 'otf') return 'font'
|
|
533
|
+
|
|
534
|
+
return 'other'
|
|
535
|
+
}
|
|
412
536
|
}
|
|
@@ -16,6 +16,9 @@ export class LiveComponentsGenerator {
|
|
|
16
16
|
private componentsPath: string
|
|
17
17
|
private registrationFilePath: string
|
|
18
18
|
private backupFilePath: string
|
|
19
|
+
private entryPointPath: string
|
|
20
|
+
private entryPointBackupPath: string
|
|
21
|
+
private readonly importLine = 'import "@/core/server/live/auto-generated-components"'
|
|
19
22
|
|
|
20
23
|
constructor() {
|
|
21
24
|
// Scan components from app/ directory (user code)
|
|
@@ -24,6 +27,10 @@ export class LiveComponentsGenerator {
|
|
|
24
27
|
// Generate registration file in core/ directory (framework code - protected from user modifications)
|
|
25
28
|
this.registrationFilePath = join(process.cwd(), 'core', 'server', 'live', 'auto-generated-components.ts')
|
|
26
29
|
this.backupFilePath = join(process.cwd(), 'core', 'server', 'live', 'auto-generated-components.backup.ts')
|
|
30
|
+
|
|
31
|
+
// Entry point for import injection
|
|
32
|
+
this.entryPointPath = join(process.cwd(), 'app', 'server', 'index.ts')
|
|
33
|
+
this.entryPointBackupPath = join(process.cwd(), 'app', 'server', 'index.ts.backup')
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
/**
|
|
@@ -147,6 +154,60 @@ ${components.map(comp => ` ${comp.className}`).join(',\n')}
|
|
|
147
154
|
}
|
|
148
155
|
}
|
|
149
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Inject the auto-generated components import into the entry point
|
|
159
|
+
*/
|
|
160
|
+
injectImportIntoEntryPoint(): void {
|
|
161
|
+
if (!existsSync(this.entryPointPath)) {
|
|
162
|
+
buildLogger.warn('Entry point not found, skipping import injection')
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const content = readFileSync(this.entryPointPath, 'utf-8')
|
|
167
|
+
|
|
168
|
+
// Check if import already exists
|
|
169
|
+
if (content.includes(this.importLine)) {
|
|
170
|
+
return // Already injected
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Backup original file
|
|
174
|
+
writeFileSync(this.entryPointBackupPath, content)
|
|
175
|
+
|
|
176
|
+
// Find the best place to inject (after other imports)
|
|
177
|
+
const lines = content.split('\n')
|
|
178
|
+
let lastImportIndex = -1
|
|
179
|
+
|
|
180
|
+
for (let i = 0; i < lines.length; i++) {
|
|
181
|
+
const line = lines[i].trim()
|
|
182
|
+
if (line.startsWith('import ') || line.startsWith('import{')) {
|
|
183
|
+
lastImportIndex = i
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Inject after the last import
|
|
188
|
+
if (lastImportIndex >= 0) {
|
|
189
|
+
lines.splice(lastImportIndex + 1, 0, this.importLine)
|
|
190
|
+
} else {
|
|
191
|
+
// No imports found, add at the beginning
|
|
192
|
+
lines.unshift(this.importLine)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
writeFileSync(this.entryPointPath, lines.join('\n'))
|
|
196
|
+
buildLogger.step('Injected auto-generated components import into entry point')
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Remove the injected import and restore original entry point
|
|
201
|
+
*/
|
|
202
|
+
removeInjectedImport(): void {
|
|
203
|
+
if (existsSync(this.entryPointBackupPath)) {
|
|
204
|
+
const backupContent = readFileSync(this.entryPointBackupPath, 'utf-8')
|
|
205
|
+
writeFileSync(this.entryPointPath, backupContent)
|
|
206
|
+
unlinkSync(this.entryPointBackupPath)
|
|
207
|
+
buildLogger.step('Restored original entry point')
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
150
211
|
/**
|
|
151
212
|
* Check if the current registration file is auto-generated
|
|
152
213
|
*/
|
|
@@ -190,11 +251,14 @@ ${components.map(comp => ` ${comp.className}`).join(',\n')}
|
|
|
190
251
|
|
|
191
252
|
this.generateRegistrationFile(components)
|
|
192
253
|
|
|
254
|
+
// Inject import into entry point for build
|
|
255
|
+
this.injectImportIntoEntryPoint()
|
|
256
|
+
|
|
193
257
|
return components
|
|
194
258
|
}
|
|
195
259
|
|
|
196
260
|
/**
|
|
197
|
-
* Post-build hook: Clean up backup file
|
|
261
|
+
* Post-build hook: Clean up backup file and restore entry point
|
|
198
262
|
* Note: Since the generated file is now in core/, we always keep it (it's bundled into production)
|
|
199
263
|
*/
|
|
200
264
|
async postBuild(keepGenerated: boolean = true): Promise<void> {
|
|
@@ -206,6 +270,9 @@ ${components.map(comp => ` ${comp.className}`).join(',\n')}
|
|
|
206
270
|
unlinkSync(this.backupFilePath)
|
|
207
271
|
buildLogger.step('Removed backup file')
|
|
208
272
|
}
|
|
273
|
+
|
|
274
|
+
// Restore original entry point (remove injected import)
|
|
275
|
+
this.removeInjectedImport()
|
|
209
276
|
}
|
|
210
277
|
|
|
211
278
|
/**
|
|
@@ -131,7 +131,7 @@ export default {{camelName}}Config
|
|
|
131
131
|
},
|
|
132
132
|
{
|
|
133
133
|
path: 'plugins/{{name}}/index.ts',
|
|
134
|
-
content: `import type {
|
|
134
|
+
content: `import type { ErrorContext, FluxStack, PluginContext, RequestContext, ResponseContext } from "@/core/plugins/types"
|
|
135
135
|
// โ
Plugin imports its own configuration
|
|
136
136
|
import { {{camelName}}Config } from './config'
|
|
137
137
|
|
|
@@ -139,7 +139,7 @@ import { {{camelName}}Config } from './config'
|
|
|
139
139
|
* {{pascalName}} Plugin
|
|
140
140
|
* {{description}}
|
|
141
141
|
*/
|
|
142
|
-
export class {{pascalName}}Plugin implements
|
|
142
|
+
export class {{pascalName}}Plugin implements FluxStack.Plugin {
|
|
143
143
|
name = '{{name}}'
|
|
144
144
|
version = '1.0.0'
|
|
145
145
|
|
|
@@ -173,7 +173,7 @@ export class {{pascalName}}Plugin implements FluxStackPlugin {
|
|
|
173
173
|
/**
|
|
174
174
|
* Request hook - called on each request
|
|
175
175
|
*/
|
|
176
|
-
async onRequest?(context:
|
|
176
|
+
async onRequest?(context: RequestContext): Promise<void> {
|
|
177
177
|
if (!{{camelName}}Config.enabled) return
|
|
178
178
|
|
|
179
179
|
// Add request processing logic
|
|
@@ -182,7 +182,7 @@ export class {{pascalName}}Plugin implements FluxStackPlugin {
|
|
|
182
182
|
/**
|
|
183
183
|
* Response hook - called on each response
|
|
184
184
|
*/
|
|
185
|
-
async onResponse?(context:
|
|
185
|
+
async onResponse?(context: ResponseContext): Promise<void> {
|
|
186
186
|
if (!{{camelName}}Config.enabled) return
|
|
187
187
|
|
|
188
188
|
// Add response processing logic
|
|
@@ -191,8 +191,8 @@ export class {{pascalName}}Plugin implements FluxStackPlugin {
|
|
|
191
191
|
/**
|
|
192
192
|
* Error hook - called when errors occur
|
|
193
193
|
*/
|
|
194
|
-
async onError?(context:
|
|
195
|
-
console.error(\`[{{name}}] Error:\`, error)
|
|
194
|
+
async onError?(context: ErrorContext): Promise<void> {
|
|
195
|
+
console.error(\`[{{name}}] Error:\`, context.error)
|
|
196
196
|
|
|
197
197
|
// Add error handling logic
|
|
198
198
|
}
|