create-fluxstack 1.18.1 → 1.19.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/CHANGELOG.md +132 -0
- package/app/client/src/App.tsx +7 -7
- package/app/client/src/components/AppLayout.tsx +60 -23
- package/app/client/src/components/ColorWheel.tsx +195 -0
- package/app/client/src/components/DemoPage.tsx +5 -3
- package/app/client/src/components/LiveUploadWidget.tsx +1 -1
- package/app/client/src/components/ThemePicker.tsx +307 -0
- package/app/client/src/config/theme.config.ts +127 -0
- package/app/client/src/hooks/useThemeClock.ts +66 -0
- package/app/client/src/index.css +193 -0
- package/app/client/src/lib/theme-clock.ts +201 -0
- package/app/client/src/live/AuthDemo.tsx +9 -9
- package/app/client/src/live/CounterDemo.tsx +10 -10
- package/app/client/src/live/FormDemo.tsx +8 -8
- package/app/client/src/live/PingPongDemo.tsx +10 -10
- package/app/client/src/live/RoomChatDemo.tsx +10 -10
- package/app/client/src/live/SharedCounterDemo.tsx +5 -5
- package/app/client/src/pages/ApiTestPage.tsx +5 -5
- package/app/client/src/pages/HomePage.tsx +12 -12
- package/app/server/index.ts +8 -0
- package/app/server/live/auto-generated-components.ts +1 -1
- package/core/build/index.ts +1 -1
- package/core/cli/command-registry.ts +1 -1
- package/core/cli/commands/build.ts +25 -6
- package/core/cli/commands/plugin-deps.ts +1 -2
- package/core/cli/generators/plugin.ts +433 -581
- package/core/framework/server.ts +22 -8
- package/core/index.ts +6 -5
- package/core/plugins/index.ts +71 -199
- package/core/plugins/types.ts +76 -461
- package/core/server/index.ts +1 -1
- package/core/utils/logger/startup-banner.ts +26 -4
- package/core/utils/version.ts +6 -6
- package/create-fluxstack.ts +216 -107
- package/package.json +6 -5
- package/tsconfig.json +2 -1
- package/app/client/.live-stubs/LiveAdminPanel.js +0 -15
- package/app/client/.live-stubs/LiveCounter.js +0 -9
- package/app/client/.live-stubs/LiveForm.js +0 -11
- package/app/client/.live-stubs/LiveLocalCounter.js +0 -8
- package/app/client/.live-stubs/LivePingPong.js +0 -10
- package/app/client/.live-stubs/LiveRoomChat.js +0 -11
- package/app/client/.live-stubs/LiveSharedCounter.js +0 -10
- package/app/client/.live-stubs/LiveUpload.js +0 -15
- package/core/plugins/config.ts +0 -356
- package/core/plugins/dependency-manager.ts +0 -481
- package/core/plugins/discovery.ts +0 -379
- package/core/plugins/executor.ts +0 -353
- package/core/plugins/manager.ts +0 -645
- package/core/plugins/module-resolver.ts +0 -227
- package/core/plugins/registry.ts +0 -913
- package/vitest.config.live.ts +0 -69
package/core/plugins/config.ts
DELETED
|
@@ -1,356 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Plugin Configuration Management
|
|
3
|
-
* Handles plugin-specific configuration validation and management
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { FluxStack, PluginConfigSchema, PluginValidationResult } from "./types"
|
|
7
|
-
import type { FluxStackConfig } from "@config"
|
|
8
|
-
import type { Logger } from "@core/utils/logger/index"
|
|
9
|
-
|
|
10
|
-
type Plugin = FluxStack.Plugin
|
|
11
|
-
|
|
12
|
-
export interface PluginConfigManager {
|
|
13
|
-
validatePluginConfig(plugin: Plugin, config: unknown): PluginValidationResult
|
|
14
|
-
mergePluginConfig(plugin: Plugin, userConfig: unknown): unknown
|
|
15
|
-
getPluginConfig(pluginName: string, config: FluxStackConfig): unknown
|
|
16
|
-
setPluginConfig(pluginName: string, pluginConfig: unknown, config: FluxStackConfig): void
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export class DefaultPluginConfigManager implements PluginConfigManager {
|
|
20
|
-
constructor(_logger?: Logger) {
|
|
21
|
-
// Logger stored but not used in current implementation
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Validate plugin configuration against its schema
|
|
26
|
-
*/
|
|
27
|
-
validatePluginConfig(plugin: Plugin, config: unknown): PluginValidationResult {
|
|
28
|
-
const result: PluginValidationResult = {
|
|
29
|
-
valid: true,
|
|
30
|
-
errors: [],
|
|
31
|
-
warnings: []
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (!plugin.configSchema) {
|
|
35
|
-
// No schema means any config is valid
|
|
36
|
-
return result
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
this.validateAgainstSchema(config, plugin.configSchema, plugin.name, result)
|
|
41
|
-
} catch (error) {
|
|
42
|
-
result.valid = false
|
|
43
|
-
result.errors.push(`Configuration validation failed: ${error instanceof Error ? error.message : String(error)}`)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return result
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Merge user configuration with plugin defaults
|
|
51
|
-
*/
|
|
52
|
-
mergePluginConfig(plugin: Plugin, userConfig: unknown): unknown {
|
|
53
|
-
const defaultConfig = (plugin.defaultConfig || {}) as Record<string, unknown>
|
|
54
|
-
|
|
55
|
-
if (!userConfig) {
|
|
56
|
-
return defaultConfig
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return this.deepMerge(defaultConfig, userConfig as Record<string, unknown>)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Get plugin configuration from main config
|
|
64
|
-
* @deprecated Plugin configs are now directly accessed from config.plugins
|
|
65
|
-
*/
|
|
66
|
-
getPluginConfig(pluginName: string, config: FluxStackConfig): unknown {
|
|
67
|
-
// Plugin configs are now accessed directly from config.plugins
|
|
68
|
-
// Example: config.plugins.swaggerEnabled
|
|
69
|
-
return {}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Set plugin configuration in main config
|
|
74
|
-
* @deprecated Plugin configs are now set via environment variables and config files
|
|
75
|
-
*/
|
|
76
|
-
setPluginConfig(pluginName: string, pluginConfig: unknown, config: FluxStackConfig): void {
|
|
77
|
-
// Plugin configs are now set via environment variables and config files
|
|
78
|
-
// This function is deprecated and does nothing
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Validate configuration against JSON schema
|
|
83
|
-
*/
|
|
84
|
-
private validateAgainstSchema(
|
|
85
|
-
data: unknown,
|
|
86
|
-
schema: PluginConfigSchema,
|
|
87
|
-
pluginName: string,
|
|
88
|
-
result: PluginValidationResult
|
|
89
|
-
): void {
|
|
90
|
-
if (schema.type === 'object' && typeof data !== 'object') {
|
|
91
|
-
result.valid = false
|
|
92
|
-
result.errors.push(`Plugin '${pluginName}' configuration must be an object`)
|
|
93
|
-
return
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const dataObj = data as Record<string, unknown>
|
|
97
|
-
|
|
98
|
-
// Check required properties
|
|
99
|
-
if (schema.required && Array.isArray(schema.required)) {
|
|
100
|
-
for (const requiredProp of schema.required) {
|
|
101
|
-
if (!(requiredProp in dataObj)) {
|
|
102
|
-
result.valid = false
|
|
103
|
-
result.errors.push(`Plugin '${pluginName}' configuration missing required property: ${requiredProp}`)
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Validate properties
|
|
109
|
-
if (schema.properties) {
|
|
110
|
-
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
111
|
-
if (propName in dataObj) {
|
|
112
|
-
this.validateProperty(dataObj[propName], propSchema as Record<string, unknown>, `${pluginName}.${propName}`, result)
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Check for additional properties
|
|
118
|
-
if (schema.additionalProperties === false) {
|
|
119
|
-
const allowedProps = Object.keys(schema.properties || {})
|
|
120
|
-
const actualProps = Object.keys(dataObj)
|
|
121
|
-
|
|
122
|
-
for (const prop of actualProps) {
|
|
123
|
-
if (!allowedProps.includes(prop)) {
|
|
124
|
-
result.warnings.push(`Plugin '${pluginName}' configuration has unexpected property: ${prop}`)
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Validate individual property
|
|
132
|
-
*/
|
|
133
|
-
private validateProperty(value: unknown, schema: Record<string, unknown>, path: string, result: PluginValidationResult): void {
|
|
134
|
-
if (schema.type) {
|
|
135
|
-
const actualType = Array.isArray(value) ? 'array' : typeof value
|
|
136
|
-
if (actualType !== schema.type) {
|
|
137
|
-
result.valid = false
|
|
138
|
-
result.errors.push(`Property '${path}' must be of type ${schema.type}, got ${actualType}`)
|
|
139
|
-
return
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Type-specific validations
|
|
144
|
-
switch (schema.type) {
|
|
145
|
-
case 'string':
|
|
146
|
-
this.validateStringProperty(value as string, schema, path, result)
|
|
147
|
-
break
|
|
148
|
-
case 'number':
|
|
149
|
-
this.validateNumberProperty(value as number, schema, path, result)
|
|
150
|
-
break
|
|
151
|
-
case 'array':
|
|
152
|
-
this.validateArrayProperty(value as unknown[], schema, path, result)
|
|
153
|
-
break
|
|
154
|
-
case 'object':
|
|
155
|
-
if (schema.properties) {
|
|
156
|
-
this.validateObjectProperty(value, schema, path, result)
|
|
157
|
-
}
|
|
158
|
-
break
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Enum validation
|
|
162
|
-
if (schema.enum && !(schema.enum as unknown[]).includes(value)) {
|
|
163
|
-
result.valid = false
|
|
164
|
-
result.errors.push(`Property '${path}' must be one of: ${(schema.enum as unknown[]).join(', ')}`)
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Validate string property
|
|
170
|
-
*/
|
|
171
|
-
private validateStringProperty(value: string, schema: Record<string, unknown>, path: string, result: PluginValidationResult): void {
|
|
172
|
-
if (schema.minLength && value.length < (schema.minLength as number)) {
|
|
173
|
-
result.valid = false
|
|
174
|
-
result.errors.push(`Property '${path}' must be at least ${schema.minLength} characters long`)
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (schema.maxLength && value.length > (schema.maxLength as number)) {
|
|
178
|
-
result.valid = false
|
|
179
|
-
result.errors.push(`Property '${path}' must be at most ${schema.maxLength} characters long`)
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (schema.pattern) {
|
|
183
|
-
const regex = new RegExp(schema.pattern as string)
|
|
184
|
-
if (!regex.test(value)) {
|
|
185
|
-
result.valid = false
|
|
186
|
-
result.errors.push(`Property '${path}' does not match required pattern: ${schema.pattern}`)
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Validate number property
|
|
193
|
-
*/
|
|
194
|
-
private validateNumberProperty(value: number, schema: Record<string, unknown>, path: string, result: PluginValidationResult): void {
|
|
195
|
-
if (schema.minimum !== undefined && value < (schema.minimum as number)) {
|
|
196
|
-
result.valid = false
|
|
197
|
-
result.errors.push(`Property '${path}' must be at least ${schema.minimum}`)
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (schema.maximum !== undefined && value > (schema.maximum as number)) {
|
|
201
|
-
result.valid = false
|
|
202
|
-
result.errors.push(`Property '${path}' must be at most ${schema.maximum}`)
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
if (schema.multipleOf && value % (schema.multipleOf as number) !== 0) {
|
|
206
|
-
result.valid = false
|
|
207
|
-
result.errors.push(`Property '${path}' must be a multiple of ${schema.multipleOf}`)
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Validate array property
|
|
213
|
-
*/
|
|
214
|
-
private validateArrayProperty(value: unknown[], schema: Record<string, unknown>, path: string, result: PluginValidationResult): void {
|
|
215
|
-
if (schema.minItems && value.length < (schema.minItems as number)) {
|
|
216
|
-
result.valid = false
|
|
217
|
-
result.errors.push(`Property '${path}' must have at least ${schema.minItems} items`)
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (schema.maxItems && value.length > (schema.maxItems as number)) {
|
|
221
|
-
result.valid = false
|
|
222
|
-
result.errors.push(`Property '${path}' must have at most ${schema.maxItems} items`)
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
if (schema.items) {
|
|
226
|
-
value.forEach((item, index) => {
|
|
227
|
-
this.validateProperty(item, schema.items as Record<string, unknown>, `${path}[${index}]`, result)
|
|
228
|
-
})
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Validate object property
|
|
234
|
-
*/
|
|
235
|
-
private validateObjectProperty(value: unknown, schema: Record<string, unknown>, path: string, result: PluginValidationResult): void {
|
|
236
|
-
const valueObj = value as Record<string, unknown>
|
|
237
|
-
if (schema.required) {
|
|
238
|
-
for (const requiredProp of (schema.required as string[])) {
|
|
239
|
-
if (!(requiredProp in valueObj)) {
|
|
240
|
-
result.valid = false
|
|
241
|
-
result.errors.push(`Property '${path}' missing required property: ${requiredProp}`)
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
if (schema.properties) {
|
|
247
|
-
for (const [propName, propSchema] of Object.entries(schema.properties as Record<string, unknown>)) {
|
|
248
|
-
if (propName in valueObj) {
|
|
249
|
-
this.validateProperty(valueObj[propName], propSchema as Record<string, unknown>, `${path}.${propName}`, result)
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Deep merge two objects
|
|
257
|
-
*/
|
|
258
|
-
deepMerge(target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown> {
|
|
259
|
-
if (source === null || source === undefined) {
|
|
260
|
-
return target
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
if (target === null || target === undefined) {
|
|
264
|
-
return source
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
if (typeof target !== 'object' || typeof source !== 'object') {
|
|
268
|
-
return source
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
if (Array.isArray(source)) {
|
|
272
|
-
return [...source] as unknown as Record<string, unknown>
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
const result = { ...target }
|
|
276
|
-
|
|
277
|
-
for (const key in source) {
|
|
278
|
-
if (source.hasOwnProperty(key)) {
|
|
279
|
-
if (typeof source[key] === 'object' && !Array.isArray(source[key]) && source[key] !== null) {
|
|
280
|
-
result[key] = this.deepMerge(target[key] as Record<string, unknown>, source[key] as Record<string, unknown>)
|
|
281
|
-
} else {
|
|
282
|
-
result[key] = source[key]
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return result
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/** Shared instance — stateless, safe to reuse across all plugin utils */
|
|
292
|
-
const sharedConfigManager = new DefaultPluginConfigManager()
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Create plugin configuration utilities
|
|
296
|
-
*/
|
|
297
|
-
export function createPluginUtils(logger?: Logger): PluginUtils {
|
|
298
|
-
return {
|
|
299
|
-
createTimer: (label: string) => {
|
|
300
|
-
const start = Date.now()
|
|
301
|
-
return {
|
|
302
|
-
end: () => {
|
|
303
|
-
const duration = Date.now() - start
|
|
304
|
-
logger?.debug(`Timer '${label}' completed`, { duration })
|
|
305
|
-
return duration
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
},
|
|
309
|
-
|
|
310
|
-
formatBytes: (bytes: number): string => {
|
|
311
|
-
if (bytes === 0) return '0 Bytes'
|
|
312
|
-
const k = 1024
|
|
313
|
-
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
|
|
314
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
315
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
|
316
|
-
},
|
|
317
|
-
|
|
318
|
-
isProduction: (): boolean => {
|
|
319
|
-
return process.env.NODE_ENV === 'production'
|
|
320
|
-
},
|
|
321
|
-
|
|
322
|
-
isDevelopment: (): boolean => {
|
|
323
|
-
return process.env.NODE_ENV === 'development'
|
|
324
|
-
},
|
|
325
|
-
|
|
326
|
-
getEnvironment: (): string => {
|
|
327
|
-
return process.env.NODE_ENV || 'development'
|
|
328
|
-
},
|
|
329
|
-
|
|
330
|
-
createHash: (data: string): string => {
|
|
331
|
-
// Simple hash function - in production, use crypto
|
|
332
|
-
let hash = 0
|
|
333
|
-
for (let i = 0; i < data.length; i++) {
|
|
334
|
-
const char = data.charCodeAt(i)
|
|
335
|
-
hash = ((hash << 5) - hash) + char
|
|
336
|
-
hash = hash & hash // Convert to 32-bit integer
|
|
337
|
-
}
|
|
338
|
-
return hash.toString(36)
|
|
339
|
-
},
|
|
340
|
-
|
|
341
|
-
deepMerge: (target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown> => {
|
|
342
|
-
return sharedConfigManager.deepMerge(target, source)
|
|
343
|
-
},
|
|
344
|
-
|
|
345
|
-
validateSchema: (data: Record<string, unknown>, schema: PluginConfigSchema): { valid: boolean; errors: string[] } => {
|
|
346
|
-
const result = sharedConfigManager.validatePluginConfig({ name: 'temp', configSchema: schema }, data)
|
|
347
|
-
return {
|
|
348
|
-
valid: result.valid,
|
|
349
|
-
errors: result.errors
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// Export types for plugin utilities
|
|
356
|
-
import type { PluginUtils } from "./types"
|