create-fluxstack 1.18.0 → 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.
Files changed (54) hide show
  1. package/CHANGELOG.md +132 -0
  2. package/app/client/src/App.tsx +7 -7
  3. package/app/client/src/components/AppLayout.tsx +60 -23
  4. package/app/client/src/components/ColorWheel.tsx +195 -0
  5. package/app/client/src/components/DemoPage.tsx +5 -3
  6. package/app/client/src/components/LiveUploadWidget.tsx +1 -1
  7. package/app/client/src/components/ThemePicker.tsx +307 -0
  8. package/app/client/src/config/theme.config.ts +127 -0
  9. package/app/client/src/hooks/useThemeClock.ts +66 -0
  10. package/app/client/src/index.css +193 -0
  11. package/app/client/src/lib/theme-clock.ts +201 -0
  12. package/app/client/src/live/AuthDemo.tsx +9 -9
  13. package/app/client/src/live/CounterDemo.tsx +10 -10
  14. package/app/client/src/live/FormDemo.tsx +8 -8
  15. package/app/client/src/live/PingPongDemo.tsx +10 -10
  16. package/app/client/src/live/RoomChatDemo.tsx +10 -10
  17. package/app/client/src/live/SharedCounterDemo.tsx +5 -5
  18. package/app/client/src/pages/ApiTestPage.tsx +5 -5
  19. package/app/client/src/pages/HomePage.tsx +12 -12
  20. package/app/server/index.ts +8 -0
  21. package/app/server/live/auto-generated-components.ts +1 -1
  22. package/app/server/live/rooms/ChatRoom.ts +13 -8
  23. package/app/server/routes/index.ts +20 -10
  24. package/core/build/index.ts +1 -1
  25. package/core/cli/command-registry.ts +1 -1
  26. package/core/cli/commands/build.ts +25 -6
  27. package/core/cli/commands/plugin-deps.ts +1 -2
  28. package/core/cli/generators/plugin.ts +433 -581
  29. package/core/framework/server.ts +34 -8
  30. package/core/index.ts +6 -5
  31. package/core/plugins/index.ts +71 -199
  32. package/core/plugins/types.ts +76 -461
  33. package/core/server/index.ts +1 -1
  34. package/core/utils/logger/startup-banner.ts +26 -4
  35. package/core/utils/version.ts +6 -6
  36. package/create-fluxstack.ts +216 -107
  37. package/package.json +108 -107
  38. package/tsconfig.json +2 -1
  39. package/app/client/.live-stubs/LiveAdminPanel.js +0 -15
  40. package/app/client/.live-stubs/LiveCounter.js +0 -9
  41. package/app/client/.live-stubs/LiveForm.js +0 -11
  42. package/app/client/.live-stubs/LiveLocalCounter.js +0 -8
  43. package/app/client/.live-stubs/LivePingPong.js +0 -10
  44. package/app/client/.live-stubs/LiveRoomChat.js +0 -11
  45. package/app/client/.live-stubs/LiveSharedCounter.js +0 -10
  46. package/app/client/.live-stubs/LiveUpload.js +0 -15
  47. package/core/plugins/config.ts +0 -356
  48. package/core/plugins/dependency-manager.ts +0 -481
  49. package/core/plugins/discovery.ts +0 -379
  50. package/core/plugins/executor.ts +0 -353
  51. package/core/plugins/manager.ts +0 -645
  52. package/core/plugins/module-resolver.ts +0 -227
  53. package/core/plugins/registry.ts +0 -913
  54. package/vitest.config.live.ts +0 -69
@@ -1,379 +0,0 @@
1
- /**
2
- * Plugin Discovery System
3
- * Handles automatic discovery and loading of plugins from various sources
4
- */
5
-
6
- import type { FluxStack, PluginManifest, PluginLoadResult, PluginDiscoveryOptions } from "./types"
7
- import type { Logger } from "@core/utils/logger/index"
8
- import { readdir, readFile } from "fs/promises"
9
- import { join, resolve } from "path"
10
- import { existsSync } from "fs"
11
-
12
- type Plugin = FluxStack.Plugin
13
-
14
- export interface PluginDiscoveryConfig {
15
- logger?: Logger
16
- baseDir?: string
17
- builtInDir?: string
18
- externalDir?: string
19
- nodeModulesDir?: string
20
- }
21
-
22
- export class PluginDiscovery {
23
- private logger?: Logger
24
- private baseDir: string
25
- private builtInDir: string
26
- private externalDir: string
27
- private nodeModulesDir: string
28
-
29
- constructor(config: PluginDiscoveryConfig = {}) {
30
- this.logger = config.logger
31
- this.baseDir = config.baseDir || process.cwd()
32
- this.builtInDir = config.builtInDir || join(this.baseDir, 'core/plugins/built-in')
33
- this.externalDir = config.externalDir || join(this.baseDir, 'plugins')
34
- this.nodeModulesDir = config.nodeModulesDir || join(this.baseDir, 'node_modules')
35
- }
36
-
37
- /**
38
- * Discover all available plugins
39
- */
40
- async discoverAll(options: PluginDiscoveryOptions = {}): Promise<PluginLoadResult[]> {
41
- const results: PluginLoadResult[] = []
42
- const {
43
- includeBuiltIn = true,
44
- includeExternal = true
45
- } = options
46
-
47
- // Discover built-in plugins
48
- if (includeBuiltIn) {
49
- const builtInResults = await this.discoverBuiltInPlugins()
50
- results.push(...builtInResults)
51
- }
52
-
53
- // Discover external plugins
54
- if (includeExternal) {
55
- const externalResults = await this.discoverExternalPlugins()
56
- results.push(...externalResults)
57
-
58
- const npmResults = await this.discoverNpmPlugins()
59
- results.push(...npmResults)
60
- }
61
-
62
- return results
63
- }
64
-
65
- /**
66
- * Discover built-in plugins
67
- */
68
- async discoverBuiltInPlugins(): Promise<PluginLoadResult[]> {
69
- if (!existsSync(this.builtInDir)) {
70
- this.logger?.debug('Built-in plugins directory not found', { dir: this.builtInDir })
71
- return []
72
- }
73
-
74
- return this.discoverPluginsInDirectory(this.builtInDir, 'built-in')
75
- }
76
-
77
- /**
78
- * Discover external plugins in the plugins directory
79
- */
80
- async discoverExternalPlugins(): Promise<PluginLoadResult[]> {
81
- if (!existsSync(this.externalDir)) {
82
- this.logger?.debug('External plugins directory not found', { dir: this.externalDir })
83
- return []
84
- }
85
-
86
- return this.discoverPluginsInDirectory(this.externalDir, 'external')
87
- }
88
-
89
- /**
90
- * Discover npm-installed plugins
91
- */
92
- async discoverNpmPlugins(): Promise<PluginLoadResult[]> {
93
- if (!existsSync(this.nodeModulesDir)) {
94
- this.logger?.debug('Node modules directory not found', { dir: this.nodeModulesDir })
95
- return []
96
- }
97
-
98
- const results: PluginLoadResult[] = []
99
-
100
- try {
101
- const entries = await readdir(this.nodeModulesDir, { withFileTypes: true })
102
-
103
- for (const entry of entries) {
104
- if (entry.isDirectory() && entry.name.startsWith('fluxstack-plugin-')) {
105
- const pluginDir = join(this.nodeModulesDir, entry.name)
106
- const result = await this.loadPluginFromDirectory(pluginDir, 'npm')
107
- results.push(result)
108
- }
109
- }
110
- } catch (error) {
111
- this.logger?.error('Failed to discover npm plugins', { error })
112
- }
113
-
114
- return results
115
- }
116
-
117
- /**
118
- * Load a specific plugin by name
119
- */
120
- async loadPlugin(name: string): Promise<PluginLoadResult> {
121
- // Try built-in first
122
- const builtInPath = join(this.builtInDir, name)
123
- if (existsSync(builtInPath)) {
124
- return this.loadPluginFromDirectory(builtInPath, 'built-in')
125
- }
126
-
127
- // Try external plugins
128
- const externalPath = join(this.externalDir, name)
129
- if (existsSync(externalPath)) {
130
- return this.loadPluginFromDirectory(externalPath, 'external')
131
- }
132
-
133
- // Try npm plugins
134
- const npmPath = join(this.nodeModulesDir, `fluxstack-plugin-${name}`)
135
- if (existsSync(npmPath)) {
136
- return this.loadPluginFromDirectory(npmPath, 'npm')
137
- }
138
-
139
- return {
140
- success: false,
141
- error: `Plugin '${name}' not found in any plugin directory`
142
- }
143
- }
144
-
145
- /**
146
- * Discover plugins in a specific directory
147
- */
148
- private async discoverPluginsInDirectory(
149
- directory: string,
150
- source: 'built-in' | 'external' | 'npm'
151
- ): Promise<PluginLoadResult[]> {
152
- const results: PluginLoadResult[] = []
153
-
154
- try {
155
- const entries = await readdir(directory, { withFileTypes: true })
156
-
157
- for (const entry of entries) {
158
- if (entry.isDirectory()) {
159
- const pluginDir = join(directory, entry.name)
160
- const result = await this.loadPluginFromDirectory(pluginDir, source)
161
- results.push(result)
162
- }
163
- }
164
- } catch (error) {
165
- this.logger?.error(`Failed to discover plugins in directory '${directory}'`, { error })
166
- results.push({
167
- success: false,
168
- error: `Failed to scan directory: ${error instanceof Error ? error.message : String(error)}`
169
- })
170
- }
171
-
172
- return results
173
- }
174
-
175
- /**
176
- * Load a plugin from a specific directory
177
- */
178
- private async loadPluginFromDirectory(
179
- pluginDir: string,
180
- source: 'built-in' | 'external' | 'npm'
181
- ): Promise<PluginLoadResult> {
182
- try {
183
- // Load manifest if it exists
184
- const manifest = await this.loadPluginManifest(pluginDir)
185
-
186
- // Find the main plugin file
187
- const pluginFile = await this.findPluginFile(pluginDir)
188
- if (!pluginFile) {
189
- return {
190
- success: false,
191
- error: 'No plugin entry point found (index.ts, index.js, plugin.ts, or plugin.js)'
192
- }
193
- }
194
-
195
- // Import the plugin
196
- const pluginModule = await import(resolve(pluginFile))
197
- const plugin: Plugin = pluginModule.default || pluginModule
198
-
199
- if (!this.isValidPlugin(plugin)) {
200
- return {
201
- success: false,
202
- error: 'Invalid plugin: must export a plugin object with a name property'
203
- }
204
- }
205
-
206
- // Validate manifest compatibility
207
- const warnings: string[] = []
208
- if (manifest) {
209
- const manifestWarnings = this.validateManifestCompatibility(plugin, manifest)
210
- warnings.push(...manifestWarnings)
211
- } else {
212
- warnings.push('No plugin manifest found')
213
- }
214
-
215
- this.logger?.debug(`Loaded plugin '${plugin.name}' from ${source}`, {
216
- plugin: plugin.name,
217
- version: plugin.version,
218
- source,
219
- path: pluginDir
220
- })
221
-
222
- return {
223
- success: true,
224
- plugin,
225
- warnings
226
- }
227
- } catch (error) {
228
- this.logger?.error(`Failed to load plugin from '${pluginDir}'`, { error })
229
- return {
230
- success: false,
231
- error: error instanceof Error ? error.message : String(error)
232
- }
233
- }
234
- }
235
-
236
- /**
237
- * Load plugin manifest from directory
238
- */
239
- private async loadPluginManifest(pluginDir: string): Promise<PluginManifest | undefined> {
240
- const manifestPath = join(pluginDir, 'plugin.json')
241
-
242
- if (!existsSync(manifestPath)) {
243
- // Try package.json for npm plugins
244
- const packagePath = join(pluginDir, 'package.json')
245
- if (existsSync(packagePath)) {
246
- try {
247
- const packageContent = await readFile(packagePath, 'utf-8')
248
- const packageJson = JSON.parse(packageContent)
249
-
250
- if (packageJson.fluxstack) {
251
- return {
252
- name: packageJson.name,
253
- version: packageJson.version,
254
- description: packageJson.description || '',
255
- author: packageJson.author || '',
256
- license: packageJson.license || '',
257
- homepage: packageJson.homepage,
258
- repository: packageJson.repository,
259
- keywords: packageJson.keywords || [],
260
- dependencies: packageJson.dependencies || {},
261
- peerDependencies: packageJson.peerDependencies,
262
- fluxstack: packageJson.fluxstack
263
- }
264
- }
265
- } catch (error) {
266
- this.logger?.warn(`Failed to parse package.json in '${pluginDir}'`, { error })
267
- }
268
- }
269
- return undefined
270
- }
271
-
272
- try {
273
- const manifestContent = await readFile(manifestPath, 'utf-8')
274
- return JSON.parse(manifestContent)
275
- } catch (error) {
276
- this.logger?.warn(`Failed to parse plugin manifest in '${pluginDir}'`, { error })
277
- return undefined
278
- }
279
- }
280
-
281
- /**
282
- * Find the main plugin file in a directory
283
- */
284
- private async findPluginFile(pluginDir: string): Promise<string | null> {
285
- const possibleFiles = [
286
- 'index.ts',
287
- 'index.js',
288
- 'plugin.ts',
289
- 'plugin.js',
290
- 'src/index.ts',
291
- 'src/index.js',
292
- 'dist/index.js'
293
- ]
294
-
295
- for (const file of possibleFiles) {
296
- const filePath = join(pluginDir, file)
297
- if (existsSync(filePath)) {
298
- return filePath
299
- }
300
- }
301
-
302
- return null
303
- }
304
-
305
- /**
306
- * Validate if an object is a valid plugin
307
- */
308
- private isValidPlugin(plugin: unknown): plugin is Plugin {
309
- if (!plugin || typeof plugin !== 'object') {
310
- return false
311
- }
312
-
313
- const pluginObj = plugin as Record<string, unknown>
314
-
315
- if (typeof pluginObj.name !== 'string' || pluginObj.name.length === 0) {
316
- return false
317
- }
318
-
319
- const hookNames = [
320
- 'setup', 'onConfigLoad', 'onBeforeServerStart', 'onServerStart',
321
- 'onAfterServerStart', 'onBeforeServerStop', 'onServerStop',
322
- 'onRequest', 'onResponse', 'onError'
323
- ]
324
-
325
- for (const hook of hookNames) {
326
- if (hook in pluginObj && typeof pluginObj[hook] !== 'function') {
327
- this.logger?.warn(`Plugin "${pluginObj.name}" has invalid hook "${hook}" (expected function, got ${typeof pluginObj[hook]})`)
328
- return false
329
- }
330
- }
331
-
332
- return true
333
- }
334
-
335
- /**
336
- * Validate manifest compatibility with plugin
337
- */
338
- private validateManifestCompatibility(plugin: Plugin, manifest: PluginManifest): string[] {
339
- const warnings: string[] = []
340
-
341
- if (plugin.name !== manifest.name) {
342
- warnings.push(`Plugin name mismatch: plugin exports '${plugin.name}' but manifest declares '${manifest.name}'`)
343
- }
344
-
345
- if (plugin.version && plugin.version !== manifest.version) {
346
- warnings.push(`Plugin version mismatch: plugin exports '${plugin.version}' but manifest declares '${manifest.version}'`)
347
- }
348
-
349
- if (plugin.dependencies && manifest.fluxstack.hooks) {
350
- // Check if plugin implements the hooks declared in manifest
351
- const declaredHooks = manifest.fluxstack.hooks
352
- const implementedHooks = Object.keys(plugin).filter(key =>
353
- key.startsWith('on') || key === 'setup'
354
- )
355
-
356
- for (const hook of declaredHooks) {
357
- if (!implementedHooks.includes(hook)) {
358
- warnings.push(`Plugin declares hook '${hook}' in manifest but doesn't implement it`)
359
- }
360
- }
361
- }
362
-
363
- return warnings
364
- }
365
- }
366
-
367
- /**
368
- * @deprecated Unused — PluginRegistry handles discovery directly.
369
- * Instantiation deferred to first access to avoid side effects at module load.
370
- * Remove this export in the next major version.
371
- */
372
- let _pluginDiscovery: PluginDiscovery | undefined
373
- export function getPluginDiscovery(): PluginDiscovery {
374
- _pluginDiscovery ??= new PluginDiscovery()
375
- return _pluginDiscovery
376
- }
377
-
378
- /** @deprecated Use getPluginDiscovery() instead */
379
- export const pluginDiscovery = {} as PluginDiscovery