create-fluxstack 1.4.0 → 1.5.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 (55) hide show
  1. package/.env.example +8 -1
  2. package/CRYPTO-AUTH-MIDDLEWARE-GUIDE.md +475 -0
  3. package/CRYPTO-AUTH-MIDDLEWARES.md +473 -0
  4. package/CRYPTO-AUTH-USAGE.md +491 -0
  5. package/EXEMPLO-ROTA-PROTEGIDA.md +347 -0
  6. package/QUICK-START-CRYPTO-AUTH.md +221 -0
  7. package/app/client/src/App.tsx +4 -1
  8. package/app/client/src/pages/CryptoAuthPage.tsx +394 -0
  9. package/app/server/index.ts +4 -0
  10. package/app/server/routes/crypto-auth-demo.routes.ts +167 -0
  11. package/app/server/routes/example-with-crypto-auth.routes.ts +235 -0
  12. package/app/server/routes/exemplo-posts.routes.ts +161 -0
  13. package/app/server/routes/index.ts +5 -1
  14. package/config/index.ts +9 -1
  15. package/core/cli/generators/index.ts +5 -2
  16. package/core/cli/generators/plugin.ts +580 -0
  17. package/core/cli/generators/template-engine.ts +5 -0
  18. package/core/cli/index.ts +88 -3
  19. package/core/cli/plugin-discovery.ts +33 -12
  20. package/core/framework/server.ts +10 -0
  21. package/core/plugins/dependency-manager.ts +89 -22
  22. package/core/plugins/index.ts +4 -0
  23. package/core/plugins/manager.ts +3 -2
  24. package/core/plugins/module-resolver.ts +216 -0
  25. package/core/plugins/registry.ts +28 -1
  26. package/core/utils/logger/index.ts +4 -0
  27. package/core/utils/version.ts +1 -1
  28. package/create-fluxstack.ts +117 -8
  29. package/fluxstack.config.ts +253 -114
  30. package/package.json +117 -117
  31. package/plugins/crypto-auth/README.md +722 -172
  32. package/plugins/crypto-auth/ai-context.md +1282 -0
  33. package/plugins/crypto-auth/cli/make-protected-route.command.ts +383 -0
  34. package/plugins/crypto-auth/client/CryptoAuthClient.ts +136 -159
  35. package/plugins/crypto-auth/client/components/AuthProvider.tsx +35 -94
  36. package/plugins/crypto-auth/client/components/LoginButton.tsx +36 -53
  37. package/plugins/crypto-auth/client/components/ProtectedRoute.tsx +17 -37
  38. package/plugins/crypto-auth/client/components/index.ts +1 -4
  39. package/plugins/crypto-auth/client/index.ts +1 -1
  40. package/plugins/crypto-auth/config/index.ts +34 -0
  41. package/plugins/crypto-auth/index.ts +84 -152
  42. package/plugins/crypto-auth/package.json +65 -64
  43. package/plugins/crypto-auth/server/AuthMiddleware.ts +19 -75
  44. package/plugins/crypto-auth/server/CryptoAuthService.ts +60 -167
  45. package/plugins/crypto-auth/server/index.ts +15 -2
  46. package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +65 -0
  47. package/plugins/crypto-auth/server/middlewares/cryptoAuthOptional.ts +26 -0
  48. package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +76 -0
  49. package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +45 -0
  50. package/plugins/crypto-auth/server/middlewares/helpers.ts +140 -0
  51. package/plugins/crypto-auth/server/middlewares/index.ts +22 -0
  52. package/plugins/crypto-auth/server/middlewares.ts +19 -0
  53. package/test-crypto-auth.ts +101 -0
  54. package/plugins/crypto-auth/client/components/SessionInfo.tsx +0 -242
  55. package/plugins/crypto-auth/plugin.json +0 -29
@@ -0,0 +1,580 @@
1
+ import type { Generator } from "./index.js"
2
+ import type { GeneratorContext, GeneratorOptions, Template } from "./types.js"
3
+ import { templateEngine } from "./template-engine.js"
4
+ import { join } from "path"
5
+
6
+ export class PluginGenerator implements Generator {
7
+ name = 'plugin'
8
+ description = 'Generate a new FluxStack plugin'
9
+
10
+ async generate(context: GeneratorContext, options: GeneratorOptions): Promise<void> {
11
+ const template = this.getTemplate(options.template)
12
+
13
+ if (template.hooks?.beforeGenerate) {
14
+ await template.hooks.beforeGenerate(context, options)
15
+ }
16
+
17
+ const files = await templateEngine.processTemplate(template, context, options)
18
+
19
+ if (options.dryRun) {
20
+ console.log(`\nšŸ“‹ Would generate plugin '${options.name}':\n`)
21
+ for (const file of files) {
22
+ console.log(`${file.action === 'create' ? 'šŸ“„' : 'āœļø'} ${file.path}`)
23
+ }
24
+ return
25
+ }
26
+
27
+ await templateEngine.generateFiles(files, options.dryRun)
28
+
29
+ if (template.hooks?.afterGenerate) {
30
+ const filePaths = files.map(f => f.path)
31
+ await template.hooks.afterGenerate(context, options, filePaths)
32
+ }
33
+
34
+ console.log(`\nāœ… Generated plugin '${options.name}' with ${files.length} files`)
35
+ console.log(`\nšŸ“¦ Next steps:`)
36
+ console.log(` 1. Configure plugin in plugins/${options.name}/config/index.ts`)
37
+ console.log(` 2. Set environment variables (optional): ${options.name.toUpperCase().replace(/-/g, '_')}_*`)
38
+ console.log(` 3. Implement your plugin logic in plugins/${options.name}/index.ts`)
39
+ console.log(` 4. Add server-side code in plugins/${options.name}/server/ (optional)`)
40
+ console.log(` 5. Add client-side code in plugins/${options.name}/client/ (optional)`)
41
+ console.log(` 6. Run: bun run dev`)
42
+ }
43
+
44
+ private getTemplate(templateName?: string): Template {
45
+ switch (templateName) {
46
+ case 'full':
47
+ return this.getFullTemplate()
48
+ case 'server':
49
+ return this.getServerOnlyTemplate()
50
+ case 'client':
51
+ return this.getClientOnlyTemplate()
52
+ default:
53
+ return this.getBasicTemplate()
54
+ }
55
+ }
56
+
57
+ private getBasicTemplate(): Template {
58
+ return {
59
+ name: 'basic-plugin',
60
+ description: 'Basic plugin template with essential files',
61
+ files: [
62
+ {
63
+ path: 'plugins/{{name}}/package.json',
64
+ content: `{
65
+ "name": "@fluxstack/{{name}}-plugin",
66
+ "version": "1.0.0",
67
+ "description": "{{description}}",
68
+ "main": "index.ts",
69
+ "types": "index.ts",
70
+ "exports": {
71
+ ".": {
72
+ "import": "./index.ts",
73
+ "types": "./index.ts"
74
+ },
75
+ "./config": {
76
+ "import": "./config/index.ts",
77
+ "types": "./config/index.ts"
78
+ }
79
+ },
80
+ "keywords": [
81
+ "fluxstack",
82
+ "plugin",
83
+ "{{name}}",
84
+ "typescript"
85
+ ],
86
+ "author": "FluxStack Developer",
87
+ "license": "MIT",
88
+ "peerDependencies": {},
89
+ "dependencies": {},
90
+ "devDependencies": {
91
+ "typescript": "^5.0.0"
92
+ },
93
+ "fluxstack": {
94
+ "plugin": true,
95
+ "version": "^1.0.0",
96
+ "hooks": [
97
+ "setup",
98
+ "onServerStart"
99
+ ],
100
+ "category": "utility",
101
+ "tags": ["{{name}}"]
102
+ }
103
+ }
104
+ `
105
+ },
106
+ {
107
+ path: 'plugins/{{name}}/config/index.ts',
108
+ content: `/**
109
+ * {{pascalName}} Plugin Configuration
110
+ * Declarative config using FluxStack config system
111
+ */
112
+
113
+ import { defineConfig, config } from '@/core/utils/config-schema'
114
+
115
+ const {{camelName}}ConfigSchema = {
116
+ // Enable/disable plugin
117
+ enabled: config.boolean('{{constantName}}_ENABLED', true),
118
+
119
+ // Add your configuration options here
120
+ // Example:
121
+ // apiKey: config.string('{{constantName}}_API_KEY', ''),
122
+ // timeout: config.number('{{constantName}}_TIMEOUT', 5000),
123
+ // debug: config.boolean('{{constantName}}_DEBUG', false),
124
+ } as const
125
+
126
+ export const {{camelName}}Config = defineConfig({{camelName}}ConfigSchema)
127
+
128
+ export type {{pascalName}}Config = typeof {{camelName}}Config
129
+ export default {{camelName}}Config
130
+ `
131
+ },
132
+ {
133
+ path: 'plugins/{{name}}/index.ts',
134
+ content: `import type { FluxStackPlugin, PluginContext } from '@/core/types/plugin'
135
+ // āœ… Plugin imports its own configuration
136
+ import { {{camelName}}Config } from './config'
137
+
138
+ /**
139
+ * {{pascalName}} Plugin
140
+ * {{description}}
141
+ */
142
+ export class {{pascalName}}Plugin implements FluxStackPlugin {
143
+ name = '{{name}}'
144
+ version = '1.0.0'
145
+
146
+ /**
147
+ * Setup hook - called when plugin is loaded
148
+ */
149
+ async setup(context: PluginContext): Promise<void> {
150
+ // Check if plugin is enabled
151
+ if (!{{camelName}}Config.enabled) {
152
+ context.logger.info(\`[{{name}}] Plugin disabled by configuration\`)
153
+ return
154
+ }
155
+
156
+ console.log(\`[{{name}}] Plugin initialized\`)
157
+
158
+ // Add your initialization logic here
159
+ // Example: Register middleware, setup database connections, etc.
160
+ }
161
+
162
+ /**
163
+ * Server start hook - called when server starts
164
+ */
165
+ async onServerStart?(context: PluginContext): Promise<void> {
166
+ if (!{{camelName}}Config.enabled) return
167
+
168
+ console.log(\`[{{name}}] Server started\`)
169
+
170
+ // Add logic to run when server starts
171
+ }
172
+
173
+ /**
174
+ * Request hook - called on each request
175
+ */
176
+ async onRequest?(context: PluginContext, request: Request): Promise<void> {
177
+ if (!{{camelName}}Config.enabled) return
178
+
179
+ // Add request processing logic
180
+ }
181
+
182
+ /**
183
+ * Response hook - called on each response
184
+ */
185
+ async onResponse?(context: PluginContext, response: Response): Promise<void> {
186
+ if (!{{camelName}}Config.enabled) return
187
+
188
+ // Add response processing logic
189
+ }
190
+
191
+ /**
192
+ * Error hook - called when errors occur
193
+ */
194
+ async onError?(context: PluginContext, error: Error): Promise<void> {
195
+ console.error(\`[{{name}}] Error:\`, error)
196
+
197
+ // Add error handling logic
198
+ }
199
+ }
200
+
201
+ // Export plugin instance
202
+ export default new {{pascalName}}Plugin()
203
+ `
204
+ },
205
+ {
206
+ path: 'plugins/{{name}}/README.md',
207
+ content: `# {{pascalName}} Plugin
208
+
209
+ {{description}}
210
+
211
+ ## Installation
212
+
213
+ This plugin is already in your FluxStack project. To use it:
214
+
215
+ 1. Make sure the plugin is enabled in your configuration
216
+ 2. Install any additional dependencies (if needed):
217
+ \`\`\`bash
218
+ bun run cli plugin:deps install
219
+ \`\`\`
220
+
221
+ ## Configuration
222
+
223
+ This plugin uses FluxStack's declarative configuration system. Configure it by editing \`config/index.ts\` or by setting environment variables:
224
+
225
+ \`\`\`bash
226
+ # Enable/disable plugin
227
+ {{constantName}}_ENABLED=true
228
+
229
+ # Add your environment variables here
230
+ # Example:
231
+ # {{constantName}}_API_KEY=your-api-key
232
+ # {{constantName}}_TIMEOUT=5000
233
+ \`\`\`
234
+
235
+ The plugin's configuration is located in \`plugins/{{name}}/config/index.ts\` and is self-contained, making the plugin fully portable.
236
+
237
+ ## Usage
238
+
239
+ \`\`\`typescript
240
+ // The plugin is automatically loaded by FluxStack
241
+ // It imports its own configuration from ./config
242
+ \`\`\`
243
+
244
+ ## API
245
+
246
+ Document your plugin's API here.
247
+
248
+ ## Hooks
249
+
250
+ This plugin uses the following hooks:
251
+ - \`setup\`: Initialize plugin resources
252
+ - \`onServerStart\`: Run when server starts (optional)
253
+ - \`onRequest\`: Process incoming requests (optional)
254
+ - \`onResponse\`: Process outgoing responses (optional)
255
+ - \`onError\`: Handle errors (optional)
256
+
257
+ ## Development
258
+
259
+ To modify this plugin:
260
+
261
+ 1. Edit \`config/index.ts\` to add configuration options
262
+ 2. Edit \`index.ts\` with your logic
263
+ 3. Test with: \`bun run dev\`
264
+
265
+ ## License
266
+
267
+ MIT
268
+ `
269
+ }
270
+ ]
271
+ }
272
+ }
273
+
274
+ private getServerOnlyTemplate(): Template {
275
+ const basic = this.getBasicTemplate()
276
+ return {
277
+ ...basic,
278
+ name: 'server-plugin',
279
+ description: 'Plugin with server-side code',
280
+ files: [
281
+ {
282
+ path: 'plugins/{{name}}/package.json',
283
+ content: `{
284
+ "name": "@fluxstack/{{name}}-plugin",
285
+ "version": "1.0.0",
286
+ "description": "{{description}}",
287
+ "main": "index.ts",
288
+ "types": "index.ts",
289
+ "exports": {
290
+ ".": {
291
+ "import": "./index.ts",
292
+ "types": "./index.ts"
293
+ },
294
+ "./config": {
295
+ "import": "./config/index.ts",
296
+ "types": "./config/index.ts"
297
+ },
298
+ "./server": {
299
+ "import": "./server/index.ts",
300
+ "types": "./server/index.ts"
301
+ }
302
+ },
303
+ "keywords": [
304
+ "fluxstack",
305
+ "plugin",
306
+ "{{name}}",
307
+ "server",
308
+ "typescript"
309
+ ],
310
+ "author": "FluxStack Developer",
311
+ "license": "MIT",
312
+ "peerDependencies": {
313
+ "elysia": "^1.0.0"
314
+ },
315
+ "dependencies": {},
316
+ "devDependencies": {
317
+ "typescript": "^5.0.0"
318
+ },
319
+ "fluxstack": {
320
+ "plugin": true,
321
+ "version": "^1.0.0",
322
+ "hooks": [
323
+ "setup",
324
+ "onServerStart",
325
+ "onRequest",
326
+ "onResponse"
327
+ ],
328
+ "category": "utility",
329
+ "tags": ["{{name}}", "server"]
330
+ }
331
+ }
332
+ `
333
+ },
334
+ ...basic.files.slice(1), // Skip package.json from basic
335
+ {
336
+ path: 'plugins/{{name}}/server/index.ts',
337
+ content: `/**
338
+ * Server-side logic for {{pascalName}} plugin
339
+ */
340
+
341
+ export class {{pascalName}}Service {
342
+ async initialize() {
343
+ console.log(\`[{{name}}] Server service initialized\`)
344
+ }
345
+
346
+ // Add your server-side methods here
347
+ }
348
+
349
+ export const {{camelName}}Service = new {{pascalName}}Service()
350
+ `
351
+ }
352
+ ]
353
+ }
354
+ }
355
+
356
+ private getClientOnlyTemplate(): Template {
357
+ const basic = this.getBasicTemplate()
358
+ return {
359
+ ...basic,
360
+ name: 'client-plugin',
361
+ description: 'Plugin with client-side code',
362
+ files: [
363
+ {
364
+ path: 'plugins/{{name}}/package.json',
365
+ content: `{
366
+ "name": "@fluxstack/{{name}}-plugin",
367
+ "version": "1.0.0",
368
+ "description": "{{description}}",
369
+ "main": "index.ts",
370
+ "types": "index.ts",
371
+ "exports": {
372
+ ".": {
373
+ "import": "./index.ts",
374
+ "types": "./index.ts"
375
+ },
376
+ "./config": {
377
+ "import": "./config/index.ts",
378
+ "types": "./config/index.ts"
379
+ },
380
+ "./client": {
381
+ "import": "./client/index.ts",
382
+ "types": "./client/index.ts"
383
+ }
384
+ },
385
+ "keywords": [
386
+ "fluxstack",
387
+ "plugin",
388
+ "{{name}}",
389
+ "react",
390
+ "client",
391
+ "typescript"
392
+ ],
393
+ "author": "FluxStack Developer",
394
+ "license": "MIT",
395
+ "peerDependencies": {
396
+ "react": ">=16.8.0"
397
+ },
398
+ "peerDependenciesMeta": {
399
+ "react": {
400
+ "optional": true
401
+ }
402
+ },
403
+ "dependencies": {},
404
+ "devDependencies": {
405
+ "@types/react": "^18.0.0",
406
+ "typescript": "^5.0.0"
407
+ },
408
+ "fluxstack": {
409
+ "plugin": true,
410
+ "version": "^1.0.0",
411
+ "hooks": [
412
+ "setup",
413
+ "onServerStart"
414
+ ],
415
+ "category": "utility",
416
+ "tags": ["{{name}}", "client", "react"]
417
+ }
418
+ }
419
+ `
420
+ },
421
+ ...basic.files.slice(1), // Skip package.json from basic
422
+ {
423
+ path: 'plugins/{{name}}/client/index.ts',
424
+ content: `/**
425
+ * Client-side logic for {{pascalName}} plugin
426
+ */
427
+
428
+ export class {{pascalName}}Client {
429
+ initialize() {
430
+ console.log(\`[{{name}}] Client initialized\`)
431
+ }
432
+
433
+ // Add your client-side methods here
434
+ }
435
+
436
+ export const {{camelName}}Client = new {{pascalName}}Client()
437
+ `
438
+ }
439
+ ]
440
+ }
441
+ }
442
+
443
+ private getFullTemplate(): Template {
444
+ const basic = this.getBasicTemplate()
445
+ const server = this.getServerOnlyTemplate()
446
+ const client = this.getClientOnlyTemplate()
447
+
448
+ return {
449
+ ...basic,
450
+ name: 'full-plugin',
451
+ description: 'Complete plugin with server and client code',
452
+ files: [
453
+ {
454
+ path: 'plugins/{{name}}/package.json',
455
+ content: `{
456
+ "name": "@fluxstack/{{name}}-plugin",
457
+ "version": "1.0.0",
458
+ "description": "{{description}}",
459
+ "main": "index.ts",
460
+ "types": "index.ts",
461
+ "exports": {
462
+ ".": {
463
+ "import": "./index.ts",
464
+ "types": "./index.ts"
465
+ },
466
+ "./config": {
467
+ "import": "./config/index.ts",
468
+ "types": "./config/index.ts"
469
+ },
470
+ "./server": {
471
+ "import": "./server/index.ts",
472
+ "types": "./server/index.ts"
473
+ },
474
+ "./client": {
475
+ "import": "./client/index.ts",
476
+ "types": "./client/index.ts"
477
+ },
478
+ "./types": {
479
+ "import": "./types.ts",
480
+ "types": "./types.ts"
481
+ }
482
+ },
483
+ "keywords": [
484
+ "fluxstack",
485
+ "plugin",
486
+ "{{name}}",
487
+ "react",
488
+ "server",
489
+ "client",
490
+ "typescript"
491
+ ],
492
+ "author": "FluxStack Developer",
493
+ "license": "MIT",
494
+ "peerDependencies": {
495
+ "react": ">=16.8.0",
496
+ "elysia": "^1.0.0"
497
+ },
498
+ "peerDependenciesMeta": {
499
+ "react": {
500
+ "optional": true
501
+ }
502
+ },
503
+ "dependencies": {},
504
+ "devDependencies": {
505
+ "@types/react": "^18.0.0",
506
+ "typescript": "^5.0.0"
507
+ },
508
+ "fluxstack": {
509
+ "plugin": true,
510
+ "version": "^1.0.0",
511
+ "hooks": [
512
+ "setup",
513
+ "onServerStart",
514
+ "onRequest",
515
+ "onResponse",
516
+ "onError"
517
+ ],
518
+ "category": "utility",
519
+ "tags": ["{{name}}", "server", "client", "react"]
520
+ }
521
+ }
522
+ `
523
+ },
524
+ ...basic.files.slice(1), // Skip package.json from basic
525
+ {
526
+ path: 'plugins/{{name}}/server/index.ts',
527
+ content: `/**
528
+ * Server-side logic for {{pascalName}} plugin
529
+ */
530
+
531
+ export class {{pascalName}}Service {
532
+ async initialize() {
533
+ console.log(\`[{{name}}] Server service initialized\`)
534
+ }
535
+
536
+ // Add your server-side methods here
537
+ }
538
+
539
+ export const {{camelName}}Service = new {{pascalName}}Service()
540
+ `
541
+ },
542
+ {
543
+ path: 'plugins/{{name}}/client/index.ts',
544
+ content: `/**
545
+ * Client-side logic for {{pascalName}} plugin
546
+ */
547
+
548
+ export class {{pascalName}}Client {
549
+ initialize() {
550
+ console.log(\`[{{name}}] Client initialized\`)
551
+ }
552
+
553
+ // Add your client-side methods here
554
+ }
555
+
556
+ export const {{camelName}}Client = new {{pascalName}}Client()
557
+ `
558
+ },
559
+ {
560
+ path: 'plugins/{{name}}/types.ts',
561
+ content: `/**
562
+ * Type definitions for {{pascalName}} plugin
563
+ */
564
+
565
+ // Config types are exported from ./config/index.ts
566
+ // Import them like: import type { {{pascalName}}Config } from './config'
567
+
568
+ export interface {{pascalName}}Options {
569
+ // Add your runtime options types here
570
+ }
571
+
572
+ export interface {{pascalName}}Event {
573
+ // Add your event types here
574
+ }
575
+ `
576
+ }
577
+ ]
578
+ }
579
+ }
580
+ }
@@ -85,6 +85,7 @@ export class TemplateEngine {
85
85
  camelName: this.toCamelCase(options.name),
86
86
  pascalName: this.toPascalCase(options.name),
87
87
  snakeName: this.toSnakeCase(options.name),
88
+ constantName: this.toConstantCase(options.name), // SCREAMING_SNAKE_CASE
88
89
  timestamp: new Date().toISOString(),
89
90
  date: new Date().toLocaleDateString(),
90
91
  year: new Date().getFullYear(),
@@ -149,6 +150,10 @@ export class TemplateEngine {
149
150
  .replace(/[\s-]+/g, '_')
150
151
  .toLowerCase()
151
152
  }
153
+
154
+ private toConstantCase(str: string): string {
155
+ return this.toSnakeCase(str).toUpperCase()
156
+ }
152
157
  }
153
158
 
154
159
  export const templateEngine = new TemplateEngine()
package/core/cli/index.ts CHANGED
@@ -224,7 +224,7 @@ Examples:
224
224
  }
225
225
  })
226
226
 
227
- // Create command
227
+ // Create command
228
228
  cliRegistry.register({
229
229
  name: 'create',
230
230
  description: 'Create a new FluxStack project',
@@ -252,7 +252,7 @@ Examples:
252
252
  ],
253
253
  handler: async (args, options, context) => {
254
254
  const [projectName, template] = args
255
-
255
+
256
256
  if (!/^[a-zA-Z0-9-_]+$/.test(projectName)) {
257
257
  console.error("āŒ Project name can only contain letters, numbers, hyphens, and underscores")
258
258
  return
@@ -263,7 +263,7 @@ Examples:
263
263
  name: projectName,
264
264
  template: template as 'basic' | 'full' || 'basic'
265
265
  })
266
-
266
+
267
267
  await creator.create()
268
268
  } catch (error) {
269
269
  console.error("āŒ Failed to create project:", error instanceof Error ? error.message : String(error))
@@ -271,6 +271,91 @@ Examples:
271
271
  }
272
272
  }
273
273
  })
274
+
275
+ // Make:plugin command (shortcut for generate plugin)
276
+ cliRegistry.register({
277
+ name: 'make:plugin',
278
+ description: 'Create a new FluxStack plugin',
279
+ category: 'Plugins',
280
+ usage: 'flux make:plugin <name> [options]',
281
+ aliases: ['create:plugin'],
282
+ examples: [
283
+ 'flux make:plugin my-plugin # Create basic plugin',
284
+ 'flux make:plugin my-plugin --template full # Create full plugin with server/client',
285
+ 'flux make:plugin auth --template server # Create server-only plugin'
286
+ ],
287
+ arguments: [
288
+ {
289
+ name: 'name',
290
+ description: 'Name of the plugin to create',
291
+ required: true,
292
+ type: 'string'
293
+ }
294
+ ],
295
+ options: [
296
+ {
297
+ name: 'template',
298
+ short: 't',
299
+ description: 'Plugin template to use',
300
+ type: 'string',
301
+ choices: ['basic', 'full', 'server', 'client'],
302
+ default: 'basic'
303
+ },
304
+ {
305
+ name: 'description',
306
+ short: 'd',
307
+ description: 'Plugin description',
308
+ type: 'string',
309
+ default: 'A FluxStack plugin'
310
+ },
311
+ {
312
+ name: 'force',
313
+ short: 'f',
314
+ description: 'Overwrite existing plugin',
315
+ type: 'boolean',
316
+ default: false
317
+ }
318
+ ],
319
+ handler: async (args, options, context) => {
320
+ const [name] = args
321
+
322
+ if (!/^[a-zA-Z0-9-_]+$/.test(name)) {
323
+ console.error("āŒ Plugin name can only contain letters, numbers, hyphens, and underscores")
324
+ return
325
+ }
326
+
327
+ // Use the plugin generator
328
+ const { generatorRegistry } = await import('./generators/index.js')
329
+ const pluginGenerator = generatorRegistry.get('plugin')
330
+
331
+ if (!pluginGenerator) {
332
+ console.error("āŒ Plugin generator not found")
333
+ return
334
+ }
335
+
336
+ const generatorContext = {
337
+ workingDir: context.workingDir,
338
+ config: context.config,
339
+ logger: context.logger,
340
+ utils: context.utils
341
+ }
342
+
343
+ const generatorOptions = {
344
+ name,
345
+ template: options.template,
346
+ force: options.force,
347
+ dryRun: false,
348
+ description: options.description
349
+ }
350
+
351
+ try {
352
+ await pluginGenerator.generate(generatorContext, generatorOptions)
353
+ } catch (error) {
354
+ console.error("āŒ Failed to create plugin:", error instanceof Error ? error.message : String(error))
355
+ throw error
356
+ }
357
+ }
358
+ })
274
359
  }
275
360
 
276
361
  // Main CLI logic