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.
- 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/app/server/live/rooms/ChatRoom.ts +13 -8
- package/app/server/routes/index.ts +20 -10
- 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 +34 -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 +108 -107
- 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
|
@@ -1,581 +1,433 @@
|
|
|
1
|
-
import type { Generator } from "./index"
|
|
2
|
-
import type { GeneratorContext, GeneratorOptions, Template } from "./types"
|
|
3
|
-
import { templateEngine } from "./template-engine"
|
|
4
|
-
import { buildLogger } from "@core/utils/build-logger"
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
import {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
*
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
// Add your client-side methods here
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
export const {{camelName}}Client = new {{pascalName}}Client()
|
|
438
|
-
`
|
|
439
|
-
}
|
|
440
|
-
]
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
private getFullTemplate(): Template {
|
|
445
|
-
const basic = this.getBasicTemplate()
|
|
446
|
-
const server = this.getServerOnlyTemplate()
|
|
447
|
-
const client = this.getClientOnlyTemplate()
|
|
448
|
-
|
|
449
|
-
return {
|
|
450
|
-
...basic,
|
|
451
|
-
name: 'full-plugin',
|
|
452
|
-
description: 'Complete plugin with server and client code',
|
|
453
|
-
files: [
|
|
454
|
-
{
|
|
455
|
-
path: 'plugins/{{name}}/package.json',
|
|
456
|
-
content: `{
|
|
457
|
-
"name": "@fluxstack/{{name}}-plugin",
|
|
458
|
-
"version": "1.0.0",
|
|
459
|
-
"description": "{{description}}",
|
|
460
|
-
"main": "index.ts",
|
|
461
|
-
"types": "index.ts",
|
|
462
|
-
"exports": {
|
|
463
|
-
".": {
|
|
464
|
-
"import": "./index.ts",
|
|
465
|
-
"types": "./index.ts"
|
|
466
|
-
},
|
|
467
|
-
"./config": {
|
|
468
|
-
"import": "./config/index.ts",
|
|
469
|
-
"types": "./config/index.ts"
|
|
470
|
-
},
|
|
471
|
-
"./server": {
|
|
472
|
-
"import": "./server/index.ts",
|
|
473
|
-
"types": "./server/index.ts"
|
|
474
|
-
},
|
|
475
|
-
"./client": {
|
|
476
|
-
"import": "./client/index.ts",
|
|
477
|
-
"types": "./client/index.ts"
|
|
478
|
-
},
|
|
479
|
-
"./types": {
|
|
480
|
-
"import": "./types.ts",
|
|
481
|
-
"types": "./types.ts"
|
|
482
|
-
}
|
|
483
|
-
},
|
|
484
|
-
"keywords": [
|
|
485
|
-
"fluxstack",
|
|
486
|
-
"plugin",
|
|
487
|
-
"{{name}}",
|
|
488
|
-
"react",
|
|
489
|
-
"server",
|
|
490
|
-
"client",
|
|
491
|
-
"typescript"
|
|
492
|
-
],
|
|
493
|
-
"author": "FluxStack Developer",
|
|
494
|
-
"license": "MIT",
|
|
495
|
-
"peerDependencies": {
|
|
496
|
-
"react": ">=16.8.0",
|
|
497
|
-
"elysia": "^1.0.0"
|
|
498
|
-
},
|
|
499
|
-
"peerDependenciesMeta": {
|
|
500
|
-
"react": {
|
|
501
|
-
"optional": true
|
|
502
|
-
}
|
|
503
|
-
},
|
|
504
|
-
"dependencies": {},
|
|
505
|
-
"devDependencies": {
|
|
506
|
-
"@types/react": "^18.0.0",
|
|
507
|
-
"typescript": "^5.0.0"
|
|
508
|
-
},
|
|
509
|
-
"fluxstack": {
|
|
510
|
-
"plugin": true,
|
|
511
|
-
"version": "^1.0.0",
|
|
512
|
-
"hooks": [
|
|
513
|
-
"setup",
|
|
514
|
-
"onServerStart",
|
|
515
|
-
"onRequest",
|
|
516
|
-
"onResponse",
|
|
517
|
-
"onError"
|
|
518
|
-
],
|
|
519
|
-
"category": "utility",
|
|
520
|
-
"tags": ["{{name}}", "server", "client", "react"]
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
`
|
|
524
|
-
},
|
|
525
|
-
...basic.files.slice(1), // Skip package.json from basic
|
|
526
|
-
{
|
|
527
|
-
path: 'plugins/{{name}}/server/index.ts',
|
|
528
|
-
content: `/**
|
|
529
|
-
* Server-side logic for {{pascalName}} plugin
|
|
530
|
-
*/
|
|
531
|
-
|
|
532
|
-
export class {{pascalName}}Service {
|
|
533
|
-
async initialize() {
|
|
534
|
-
console.log(\`[{{name}}] Server service initialized\`)
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
// Add your server-side methods here
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
export const {{camelName}}Service = new {{pascalName}}Service()
|
|
541
|
-
`
|
|
542
|
-
},
|
|
543
|
-
{
|
|
544
|
-
path: 'plugins/{{name}}/client/index.ts',
|
|
545
|
-
content: `/**
|
|
546
|
-
* Client-side logic for {{pascalName}} plugin
|
|
547
|
-
*/
|
|
548
|
-
|
|
549
|
-
export class {{pascalName}}Client {
|
|
550
|
-
initialize() {
|
|
551
|
-
console.log(\`[{{name}}] Client initialized\`)
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
// Add your client-side methods here
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
export const {{camelName}}Client = new {{pascalName}}Client()
|
|
558
|
-
`
|
|
559
|
-
},
|
|
560
|
-
{
|
|
561
|
-
path: 'plugins/{{name}}/types.ts',
|
|
562
|
-
content: `/**
|
|
563
|
-
* Type definitions for {{pascalName}} plugin
|
|
564
|
-
*/
|
|
565
|
-
|
|
566
|
-
// Config types are exported from ./config/index.ts
|
|
567
|
-
// Import them like: import type { {{pascalName}}Config } from './config'
|
|
568
|
-
|
|
569
|
-
export interface {{pascalName}}Options {
|
|
570
|
-
// Add your runtime options types here
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
export interface {{pascalName}}Event {
|
|
574
|
-
// Add your event types here
|
|
575
|
-
}
|
|
576
|
-
`
|
|
577
|
-
}
|
|
578
|
-
]
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
}
|
|
1
|
+
import type { Generator } from "./index"
|
|
2
|
+
import type { GeneratorContext, GeneratorOptions, Template } from "./types"
|
|
3
|
+
import { templateEngine } from "./template-engine"
|
|
4
|
+
import { buildLogger } from "@core/utils/build-logger"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Plugin scaffolder for `bun run cli make:plugin <name>`.
|
|
8
|
+
*
|
|
9
|
+
* Emits project-local plugin skeletons into `plugins/<name>/`. The
|
|
10
|
+
* generated code follows the CURRENT plugin model:
|
|
11
|
+
*
|
|
12
|
+
* - Plugin is a plain object literal implementing `Plugin` from
|
|
13
|
+
* `@fluxstack/plugin-kit`. NOT a class.
|
|
14
|
+
* - No `plugin.json`, no auto-discovery, no `fluxstack` manifest
|
|
15
|
+
* block in `package.json`. Plugins live inside the project and
|
|
16
|
+
* are imported via relative path, then registered via
|
|
17
|
+
* `framework.use(myPlugin)` in `app/server/index.ts`.
|
|
18
|
+
* - `package.json` for a project-local plugin is optional and
|
|
19
|
+
* minimal. It is NOT an npm package ā it's just a way to let
|
|
20
|
+
* the plugin pin its own dev-time dependencies if needed. Most
|
|
21
|
+
* plugins will not need one at all.
|
|
22
|
+
* - The generator does NOT auto-edit `app/server/index.ts`. After
|
|
23
|
+
* scaffolding, it prints explicit instructions for the user to
|
|
24
|
+
* import and `.use()` the new plugin themselves.
|
|
25
|
+
*/
|
|
26
|
+
export class PluginGenerator implements Generator {
|
|
27
|
+
name = 'plugin'
|
|
28
|
+
description = 'Generate a new FluxStack plugin'
|
|
29
|
+
|
|
30
|
+
async generate(context: GeneratorContext, options: GeneratorOptions): Promise<void> {
|
|
31
|
+
const template = this.getTemplate(options.template)
|
|
32
|
+
|
|
33
|
+
// Derive identifier names that already include "Plugin" as a suffix,
|
|
34
|
+
// WITHOUT duplicating it when the user's plugin name already ends
|
|
35
|
+
// with -plugin. Examples:
|
|
36
|
+
// 'my-plugin' ā pluginIdent = myPlugin
|
|
37
|
+
// 'csrf-protection' ā pluginIdent = csrfProtectionPlugin
|
|
38
|
+
// 'my-test-plugin' ā pluginIdent = myTestPlugin
|
|
39
|
+
//
|
|
40
|
+
// These are injected into the template variables via options (the
|
|
41
|
+
// template engine spreads options into the variable bag before
|
|
42
|
+
// processing {{placeholders}}), so the templates can use
|
|
43
|
+
// {{pluginIdent}} / {{pluginIdentPascal}} directly.
|
|
44
|
+
const camelName = this.toCamelCase(options.name)
|
|
45
|
+
const pascalName = this.toPascalCase(options.name)
|
|
46
|
+
const alreadyEndsWithPlugin = /plugin$/i.test(options.name)
|
|
47
|
+
const pluginIdent = alreadyEndsWithPlugin ? camelName : `${camelName}Plugin`
|
|
48
|
+
const pluginIdentPascal = alreadyEndsWithPlugin ? pascalName : `${pascalName}Plugin`
|
|
49
|
+
|
|
50
|
+
const enrichedOptions: GeneratorOptions = {
|
|
51
|
+
...options,
|
|
52
|
+
pluginIdent,
|
|
53
|
+
pluginIdentPascal,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (template.hooks?.beforeGenerate) {
|
|
57
|
+
await template.hooks.beforeGenerate(context, enrichedOptions)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const files = await templateEngine.processTemplate(template, context, enrichedOptions)
|
|
61
|
+
|
|
62
|
+
if (options.dryRun) {
|
|
63
|
+
buildLogger.info(`\nš Would generate plugin '${options.name}':\n`)
|
|
64
|
+
for (const file of files) {
|
|
65
|
+
buildLogger.info(`${file.action === 'create' ? 'š' : 'āļø'} ${file.path}`)
|
|
66
|
+
}
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
await templateEngine.generateFiles(files, options.dryRun)
|
|
71
|
+
|
|
72
|
+
if (template.hooks?.afterGenerate) {
|
|
73
|
+
const filePaths = files.map(f => f.path)
|
|
74
|
+
await template.hooks.afterGenerate(context, enrichedOptions, filePaths)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
buildLogger.success(`Generated plugin '${options.name}' with ${files.length} files`)
|
|
78
|
+
|
|
79
|
+
const importPath = `../../plugins/${options.name}`
|
|
80
|
+
|
|
81
|
+
buildLogger.info(`\nš Next steps:`)
|
|
82
|
+
buildLogger.info(` 1. Implement your plugin logic in plugins/${options.name}/index.ts`)
|
|
83
|
+
buildLogger.info(` 2. Configure it (optional) in plugins/${options.name}/config/index.ts`)
|
|
84
|
+
buildLogger.info(``)
|
|
85
|
+
buildLogger.info(` 3. Register the plugin in app/server/index.ts:`)
|
|
86
|
+
buildLogger.info(``)
|
|
87
|
+
buildLogger.info(` import { ${pluginIdent} } from '${importPath}'`)
|
|
88
|
+
buildLogger.info(``)
|
|
89
|
+
buildLogger.info(` framework.use(${pluginIdent})`)
|
|
90
|
+
buildLogger.info(``)
|
|
91
|
+
buildLogger.info(` ā ļø Plugins are NOT auto-discovered. They must be explicitly`)
|
|
92
|
+
buildLogger.info(` registered via framework.use() to be loaded at runtime.`)
|
|
93
|
+
buildLogger.info(``)
|
|
94
|
+
buildLogger.info(` 4. Run: bun run dev`)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Local copies of the same case helpers the template engine uses
|
|
98
|
+
// internally. We need them here to precompute pluginIdent /
|
|
99
|
+
// pluginIdentPascal before the template engine runs, so we can
|
|
100
|
+
// inject them into the variable bag via enrichedOptions.
|
|
101
|
+
private toCamelCase(str: string): string {
|
|
102
|
+
return str.replace(/[-_\s]+(.)?/g, (_, c: string | undefined) =>
|
|
103
|
+
c ? c.toUpperCase() : '',
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private toPascalCase(str: string): string {
|
|
108
|
+
const camel = this.toCamelCase(str)
|
|
109
|
+
return camel.charAt(0).toUpperCase() + camel.slice(1)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private getTemplate(templateName?: string): Template {
|
|
113
|
+
switch (templateName) {
|
|
114
|
+
case 'full':
|
|
115
|
+
return this.getFullTemplate()
|
|
116
|
+
case 'server':
|
|
117
|
+
return this.getServerOnlyTemplate()
|
|
118
|
+
case 'client':
|
|
119
|
+
return this.getClientOnlyTemplate()
|
|
120
|
+
default:
|
|
121
|
+
return this.getBasicTemplate()
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private getBasicTemplate(): Template {
|
|
126
|
+
return {
|
|
127
|
+
name: 'basic-plugin',
|
|
128
|
+
description: 'Basic plugin template (plain object literal)',
|
|
129
|
+
files: [
|
|
130
|
+
{
|
|
131
|
+
path: 'plugins/{{name}}/config/index.ts',
|
|
132
|
+
content: `/**
|
|
133
|
+
* {{pascalName}} Plugin Configuration
|
|
134
|
+
* Declarative config using @fluxstack/config
|
|
135
|
+
*/
|
|
136
|
+
|
|
137
|
+
import { defineConfig, config } from '@fluxstack/config'
|
|
138
|
+
|
|
139
|
+
const {{camelName}}ConfigSchema = {
|
|
140
|
+
// Enable/disable plugin
|
|
141
|
+
enabled: config.boolean('{{constantName}}_ENABLED', true),
|
|
142
|
+
|
|
143
|
+
// Add your configuration options here. Example:
|
|
144
|
+
// apiKey: config.string('{{constantName}}_API_KEY', ''),
|
|
145
|
+
// timeout: config.number('{{constantName}}_TIMEOUT', 5000),
|
|
146
|
+
// debug: config.boolean('{{constantName}}_DEBUG', false),
|
|
147
|
+
} as const
|
|
148
|
+
|
|
149
|
+
export const {{camelName}}Config = defineConfig({{camelName}}ConfigSchema)
|
|
150
|
+
|
|
151
|
+
export type {{pascalName}}Config = typeof {{camelName}}Config
|
|
152
|
+
export default {{camelName}}Config
|
|
153
|
+
`
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
path: 'plugins/{{name}}/index.ts',
|
|
157
|
+
content: `import type {
|
|
158
|
+
Plugin,
|
|
159
|
+
PluginContext,
|
|
160
|
+
RequestContext,
|
|
161
|
+
ResponseContext,
|
|
162
|
+
ErrorContext,
|
|
163
|
+
} from '@fluxstack/plugin-kit'
|
|
164
|
+
import { {{camelName}}Config } from './config'
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* {{pascalName}} Plugin
|
|
168
|
+
* {{description}}
|
|
169
|
+
*
|
|
170
|
+
* Plugins are plain object literals implementing the \`Plugin\`
|
|
171
|
+
* interface from @fluxstack/plugin-kit. To enable this plugin,
|
|
172
|
+
* import it in app/server/index.ts and pass it to framework.use():
|
|
173
|
+
*
|
|
174
|
+
* import { {{pluginIdent}} } from '../../plugins/{{name}}'
|
|
175
|
+
*
|
|
176
|
+
* framework.use({{pluginIdent}})
|
|
177
|
+
*/
|
|
178
|
+
export const {{pluginIdent}}: Plugin = {
|
|
179
|
+
name: '{{name}}',
|
|
180
|
+
version: '1.0.0',
|
|
181
|
+
description: '{{description}}',
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Setup hook ā runs once during framework.start().
|
|
185
|
+
* Use it to initialize resources, mount Elysia routes via
|
|
186
|
+
* context.app, register client-side hooks via context.clientHooks,
|
|
187
|
+
* etc.
|
|
188
|
+
*/
|
|
189
|
+
setup: async (context: PluginContext) => {
|
|
190
|
+
if (!{{camelName}}Config.enabled) {
|
|
191
|
+
context.logger.info('[{{name}}] disabled by configuration')
|
|
192
|
+
return
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
context.logger.info('[{{name}}] initialized')
|
|
196
|
+
|
|
197
|
+
// Add your initialization logic here
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Server start hook ā runs once after the HTTP server starts listening.
|
|
202
|
+
*/
|
|
203
|
+
onServerStart: async (context: PluginContext) => {
|
|
204
|
+
if (!{{camelName}}Config.enabled) return
|
|
205
|
+
context.logger.debug('[{{name}}] server started')
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Request hook ā runs on every incoming request.
|
|
210
|
+
* Remove this if you don't need per-request behavior.
|
|
211
|
+
*/
|
|
212
|
+
onRequest: async (_context: RequestContext) => {
|
|
213
|
+
if (!{{camelName}}Config.enabled) return
|
|
214
|
+
// Add request processing logic
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Response hook ā runs on every outgoing response.
|
|
219
|
+
* Remove this if you don't need per-response behavior.
|
|
220
|
+
*/
|
|
221
|
+
onResponse: async (_context: ResponseContext) => {
|
|
222
|
+
if (!{{camelName}}Config.enabled) return
|
|
223
|
+
// Add response processing logic
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Error hook ā runs when an error is thrown from a handler.
|
|
228
|
+
*/
|
|
229
|
+
onError: async (context: ErrorContext) => {
|
|
230
|
+
console.error('[{{name}}] error:', context.error.message)
|
|
231
|
+
},
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export default {{pluginIdent}}
|
|
235
|
+
`
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
path: 'plugins/{{name}}/README.md',
|
|
239
|
+
content: `# {{pascalName}} Plugin
|
|
240
|
+
|
|
241
|
+
{{description}}
|
|
242
|
+
|
|
243
|
+
## Enabling this plugin
|
|
244
|
+
|
|
245
|
+
Plugins are **not** auto-discovered. To enable this plugin, import
|
|
246
|
+
it and register it via \`framework.use()\` in \`app/server/index.ts\`:
|
|
247
|
+
|
|
248
|
+
\`\`\`typescript
|
|
249
|
+
import { {{pluginIdent}} } from '../../plugins/{{name}}'
|
|
250
|
+
|
|
251
|
+
const framework = new FluxStackFramework()
|
|
252
|
+
.use({{pluginIdent}})
|
|
253
|
+
\`\`\`
|
|
254
|
+
|
|
255
|
+
## Configuration
|
|
256
|
+
|
|
257
|
+
This plugin uses FluxStack's declarative config system
|
|
258
|
+
(\`@fluxstack/config\`). Tweak defaults in
|
|
259
|
+
\`plugins/{{name}}/config/index.ts\`, or set environment variables:
|
|
260
|
+
|
|
261
|
+
\`\`\`bash
|
|
262
|
+
# Enable/disable the plugin
|
|
263
|
+
{{constantName}}_ENABLED=true
|
|
264
|
+
|
|
265
|
+
# Add your own environment variables here. Example:
|
|
266
|
+
# {{constantName}}_API_KEY=your-api-key
|
|
267
|
+
# {{constantName}}_TIMEOUT=5000
|
|
268
|
+
\`\`\`
|
|
269
|
+
|
|
270
|
+
The config is self-contained in the plugin folder, so this plugin
|
|
271
|
+
is fully portable ā copy the folder into another FluxStack project,
|
|
272
|
+
register it via \`.use()\`, and it works.
|
|
273
|
+
|
|
274
|
+
## Hooks this plugin uses
|
|
275
|
+
|
|
276
|
+
- \`setup\` ā initialize resources at startup
|
|
277
|
+
- \`onServerStart\` ā runs after the HTTP server binds the port
|
|
278
|
+
- \`onRequest\` ā runs on every incoming request (remove if unused)
|
|
279
|
+
- \`onResponse\` ā runs on every outgoing response (remove if unused)
|
|
280
|
+
- \`onError\` ā runs when a handler throws
|
|
281
|
+
|
|
282
|
+
See \`LLMD/reference/plugin-hooks.md\` for the full list of hooks
|
|
283
|
+
and their signatures.
|
|
284
|
+
|
|
285
|
+
## Development
|
|
286
|
+
|
|
287
|
+
1. Edit \`config/index.ts\` to add configuration options
|
|
288
|
+
2. Edit \`index.ts\` to implement your plugin logic
|
|
289
|
+
3. Register the plugin via \`framework.use()\` (see above)
|
|
290
|
+
4. Run: \`bun run dev\`
|
|
291
|
+
|
|
292
|
+
## License
|
|
293
|
+
|
|
294
|
+
MIT
|
|
295
|
+
`
|
|
296
|
+
}
|
|
297
|
+
]
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
private getServerOnlyTemplate(): Template {
|
|
302
|
+
const basic = this.getBasicTemplate()
|
|
303
|
+
return {
|
|
304
|
+
...basic,
|
|
305
|
+
name: 'server-plugin',
|
|
306
|
+
description: 'Plugin with server-side service code',
|
|
307
|
+
files: [
|
|
308
|
+
...basic.files,
|
|
309
|
+
{
|
|
310
|
+
path: 'plugins/{{name}}/server/index.ts',
|
|
311
|
+
content: `/**
|
|
312
|
+
* Server-side service for {{pascalName}} plugin
|
|
313
|
+
*
|
|
314
|
+
* Import this from plugins/{{name}}/index.ts and wire it up inside
|
|
315
|
+
* the plugin's setup hook. Keeping services as named exports (not
|
|
316
|
+
* default-exported singletons) makes them easier to test.
|
|
317
|
+
*/
|
|
318
|
+
|
|
319
|
+
export class {{pascalName}}Service {
|
|
320
|
+
async initialize(): Promise<void> {
|
|
321
|
+
console.log('[{{name}}] server service initialized')
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Add your server-side methods here
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export const {{camelName}}Service = new {{pascalName}}Service()
|
|
328
|
+
`
|
|
329
|
+
}
|
|
330
|
+
]
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
private getClientOnlyTemplate(): Template {
|
|
335
|
+
const basic = this.getBasicTemplate()
|
|
336
|
+
return {
|
|
337
|
+
...basic,
|
|
338
|
+
name: 'client-plugin',
|
|
339
|
+
description: 'Plugin with client-side code',
|
|
340
|
+
files: [
|
|
341
|
+
...basic.files,
|
|
342
|
+
{
|
|
343
|
+
path: 'plugins/{{name}}/client/index.ts',
|
|
344
|
+
content: `/**
|
|
345
|
+
* Client-side code for {{pascalName}} plugin
|
|
346
|
+
*
|
|
347
|
+
* This runs in the browser. If you need the plugin's server-side
|
|
348
|
+
* setup hook to inject a <script> tag or a fetch interceptor into
|
|
349
|
+
* the client, use \`context.clientHooks.register()\` from within
|
|
350
|
+
* the plugin's setup() in plugins/{{name}}/index.ts.
|
|
351
|
+
*/
|
|
352
|
+
|
|
353
|
+
export class {{pascalName}}Client {
|
|
354
|
+
initialize(): void {
|
|
355
|
+
console.log('[{{name}}] client initialized')
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Add your client-side methods here
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export const {{camelName}}Client = new {{pascalName}}Client()
|
|
362
|
+
`
|
|
363
|
+
}
|
|
364
|
+
]
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
private getFullTemplate(): Template {
|
|
369
|
+
const basic = this.getBasicTemplate()
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
...basic,
|
|
373
|
+
name: 'full-plugin',
|
|
374
|
+
description: 'Complete plugin with server + client code',
|
|
375
|
+
files: [
|
|
376
|
+
...basic.files,
|
|
377
|
+
{
|
|
378
|
+
path: 'plugins/{{name}}/server/index.ts',
|
|
379
|
+
content: `/**
|
|
380
|
+
* Server-side service for {{pascalName}} plugin
|
|
381
|
+
*/
|
|
382
|
+
|
|
383
|
+
export class {{pascalName}}Service {
|
|
384
|
+
async initialize(): Promise<void> {
|
|
385
|
+
console.log('[{{name}}] server service initialized')
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Add your server-side methods here
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export const {{camelName}}Service = new {{pascalName}}Service()
|
|
392
|
+
`
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
path: 'plugins/{{name}}/client/index.ts',
|
|
396
|
+
content: `/**
|
|
397
|
+
* Client-side code for {{pascalName}} plugin
|
|
398
|
+
*/
|
|
399
|
+
|
|
400
|
+
export class {{pascalName}}Client {
|
|
401
|
+
initialize(): void {
|
|
402
|
+
console.log('[{{name}}] client initialized')
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Add your client-side methods here
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export const {{camelName}}Client = new {{pascalName}}Client()
|
|
409
|
+
`
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
path: 'plugins/{{name}}/types.ts',
|
|
413
|
+
content: `/**
|
|
414
|
+
* Type definitions for {{pascalName}} plugin
|
|
415
|
+
*/
|
|
416
|
+
|
|
417
|
+
// Config types are exported from ./config/index.ts
|
|
418
|
+
// Import them like:
|
|
419
|
+
// import type { {{pascalName}}Config } from './config'
|
|
420
|
+
|
|
421
|
+
export interface {{pascalName}}Options {
|
|
422
|
+
// Add your runtime options types here
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
export interface {{pascalName}}Event {
|
|
426
|
+
// Add your event types here
|
|
427
|
+
}
|
|
428
|
+
`
|
|
429
|
+
}
|
|
430
|
+
]
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|