create-fluxstack 1.4.0 ā 1.4.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/core/cli/generators/index.ts +5 -2
- package/core/cli/generators/plugin.ts +290 -0
- package/core/cli/index.ts +88 -3
- package/core/utils/version.ts +1 -1
- package/create-fluxstack.ts +117 -8
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@ import { ControllerGenerator } from "./controller.js"
|
|
|
3
3
|
import { RouteGenerator } from "./route.js"
|
|
4
4
|
import { ComponentGenerator } from "./component.js"
|
|
5
5
|
import { ServiceGenerator } from "./service.js"
|
|
6
|
+
import { PluginGenerator } from "./plugin.js"
|
|
6
7
|
import type { GeneratorContext, GeneratorOptions } from "./types.js"
|
|
7
8
|
|
|
8
9
|
export interface Generator {
|
|
@@ -23,6 +24,7 @@ export class GeneratorRegistry {
|
|
|
23
24
|
this.register(new RouteGenerator())
|
|
24
25
|
this.register(new ComponentGenerator())
|
|
25
26
|
this.register(new ServiceGenerator())
|
|
27
|
+
this.register(new PluginGenerator())
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
register(generator: Generator): void {
|
|
@@ -58,7 +60,8 @@ export const generateCommand: CliCommand = {
|
|
|
58
60
|
'flux generate controller user',
|
|
59
61
|
'flux generate component UserCard',
|
|
60
62
|
'flux generate service auth',
|
|
61
|
-
'flux generate route api/users'
|
|
63
|
+
'flux generate route api/users',
|
|
64
|
+
'flux generate plugin my-plugin'
|
|
62
65
|
],
|
|
63
66
|
arguments: [
|
|
64
67
|
{
|
|
@@ -66,7 +69,7 @@ export const generateCommand: CliCommand = {
|
|
|
66
69
|
description: 'Type of code to generate',
|
|
67
70
|
required: true,
|
|
68
71
|
type: 'string',
|
|
69
|
-
choices: ['controller', 'route', 'component', 'service']
|
|
72
|
+
choices: ['controller', 'route', 'component', 'service', 'plugin']
|
|
70
73
|
},
|
|
71
74
|
{
|
|
72
75
|
name: 'name',
|
|
@@ -0,0 +1,290 @@
|
|
|
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. Edit plugins/${options.name}/plugin.json with your plugin metadata`)
|
|
37
|
+
console.log(` 2. Implement your plugin logic in plugins/${options.name}/index.ts`)
|
|
38
|
+
console.log(` 3. Add server-side code in plugins/${options.name}/server/ (optional)`)
|
|
39
|
+
console.log(` 4. Add client-side code in plugins/${options.name}/client/ (optional)`)
|
|
40
|
+
console.log(` 5. Run: bun run cli plugin:deps install`)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private getTemplate(templateName?: string): Template {
|
|
44
|
+
switch (templateName) {
|
|
45
|
+
case 'full':
|
|
46
|
+
return this.getFullTemplate()
|
|
47
|
+
case 'server':
|
|
48
|
+
return this.getServerOnlyTemplate()
|
|
49
|
+
case 'client':
|
|
50
|
+
return this.getClientOnlyTemplate()
|
|
51
|
+
default:
|
|
52
|
+
return this.getBasicTemplate()
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private getBasicTemplate(): Template {
|
|
57
|
+
return {
|
|
58
|
+
name: 'basic-plugin',
|
|
59
|
+
description: 'Basic plugin template with essential files',
|
|
60
|
+
files: [
|
|
61
|
+
{
|
|
62
|
+
path: 'plugins/{{name}}/plugin.json',
|
|
63
|
+
content: `{
|
|
64
|
+
"name": "{{name}}",
|
|
65
|
+
"version": "1.0.0",
|
|
66
|
+
"description": "{{description}}",
|
|
67
|
+
"author": "Your Name",
|
|
68
|
+
"type": "fluxstack-plugin",
|
|
69
|
+
"main": "index.ts",
|
|
70
|
+
"dependencies": {},
|
|
71
|
+
"fluxstack": {
|
|
72
|
+
"minVersion": "1.4.0"
|
|
73
|
+
},
|
|
74
|
+
"hooks": {
|
|
75
|
+
"setup": true,
|
|
76
|
+
"onServerStart": false,
|
|
77
|
+
"onRequest": false,
|
|
78
|
+
"onResponse": false,
|
|
79
|
+
"onError": false
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
`
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
path: 'plugins/{{name}}/index.ts',
|
|
86
|
+
content: `import type { FluxStackPlugin, PluginContext } from '@/core/types/plugin'
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* {{pascalName}} Plugin
|
|
90
|
+
* {{description}}
|
|
91
|
+
*/
|
|
92
|
+
export class {{pascalName}}Plugin implements FluxStackPlugin {
|
|
93
|
+
name = '{{name}}'
|
|
94
|
+
version = '1.0.0'
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Setup hook - called when plugin is loaded
|
|
98
|
+
*/
|
|
99
|
+
async setup(context: PluginContext): Promise<void> {
|
|
100
|
+
console.log(\`[{{name}}] Plugin initialized\`)
|
|
101
|
+
|
|
102
|
+
// Add your initialization logic here
|
|
103
|
+
// Example: Register middleware, setup database connections, etc.
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Server start hook - called when server starts
|
|
108
|
+
*/
|
|
109
|
+
async onServerStart?(context: PluginContext): Promise<void> {
|
|
110
|
+
console.log(\`[{{name}}] Server started\`)
|
|
111
|
+
|
|
112
|
+
// Add logic to run when server starts
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Request hook - called on each request
|
|
117
|
+
*/
|
|
118
|
+
async onRequest?(context: PluginContext, request: Request): Promise<void> {
|
|
119
|
+
// Add request processing logic
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Response hook - called on each response
|
|
124
|
+
*/
|
|
125
|
+
async onResponse?(context: PluginContext, response: Response): Promise<void> {
|
|
126
|
+
// Add response processing logic
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Error hook - called when errors occur
|
|
131
|
+
*/
|
|
132
|
+
async onError?(context: PluginContext, error: Error): Promise<void> {
|
|
133
|
+
console.error(\`[{{name}}] Error:\`, error)
|
|
134
|
+
|
|
135
|
+
// Add error handling logic
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Export plugin instance
|
|
140
|
+
export default new {{pascalName}}Plugin()
|
|
141
|
+
`
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
path: 'plugins/{{name}}/README.md',
|
|
145
|
+
content: `# {{pascalName}} Plugin
|
|
146
|
+
|
|
147
|
+
{{description}}
|
|
148
|
+
|
|
149
|
+
## Installation
|
|
150
|
+
|
|
151
|
+
This plugin is already in your FluxStack project. To use it:
|
|
152
|
+
|
|
153
|
+
1. Make sure the plugin is enabled in your configuration
|
|
154
|
+
2. Install any additional dependencies (if needed):
|
|
155
|
+
\`\`\`bash
|
|
156
|
+
bun run cli plugin:deps install
|
|
157
|
+
\`\`\`
|
|
158
|
+
|
|
159
|
+
## Usage
|
|
160
|
+
|
|
161
|
+
\`\`\`typescript
|
|
162
|
+
// The plugin is automatically loaded by FluxStack
|
|
163
|
+
// Configure it in your app/server/index.ts if needed
|
|
164
|
+
\`\`\`
|
|
165
|
+
|
|
166
|
+
## Configuration
|
|
167
|
+
|
|
168
|
+
Add configuration options here.
|
|
169
|
+
|
|
170
|
+
## API
|
|
171
|
+
|
|
172
|
+
Document your plugin's API here.
|
|
173
|
+
|
|
174
|
+
## Hooks
|
|
175
|
+
|
|
176
|
+
This plugin uses the following hooks:
|
|
177
|
+
- \`setup\`: Initialize plugin resources
|
|
178
|
+
- \`onServerStart\`: Run when server starts (optional)
|
|
179
|
+
- \`onRequest\`: Process incoming requests (optional)
|
|
180
|
+
- \`onResponse\`: Process outgoing responses (optional)
|
|
181
|
+
- \`onError\`: Handle errors (optional)
|
|
182
|
+
|
|
183
|
+
## Development
|
|
184
|
+
|
|
185
|
+
To modify this plugin:
|
|
186
|
+
|
|
187
|
+
1. Edit \`index.ts\` with your logic
|
|
188
|
+
2. Update \`plugin.json\` with metadata
|
|
189
|
+
3. Test with: \`bun run dev\`
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
MIT
|
|
194
|
+
`
|
|
195
|
+
}
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private getServerOnlyTemplate(): Template {
|
|
201
|
+
const basic = this.getBasicTemplate()
|
|
202
|
+
return {
|
|
203
|
+
...basic,
|
|
204
|
+
name: 'server-plugin',
|
|
205
|
+
description: 'Plugin with server-side code',
|
|
206
|
+
files: [
|
|
207
|
+
...basic.files,
|
|
208
|
+
{
|
|
209
|
+
path: 'plugins/{{name}}/server/index.ts',
|
|
210
|
+
content: `/**
|
|
211
|
+
* Server-side logic for {{pascalName}} plugin
|
|
212
|
+
*/
|
|
213
|
+
|
|
214
|
+
export class {{pascalName}}Service {
|
|
215
|
+
async initialize() {
|
|
216
|
+
console.log(\`[{{name}}] Server service initialized\`)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Add your server-side methods here
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export const {{camelName}}Service = new {{pascalName}}Service()
|
|
223
|
+
`
|
|
224
|
+
}
|
|
225
|
+
]
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private getClientOnlyTemplate(): Template {
|
|
230
|
+
const basic = this.getBasicTemplate()
|
|
231
|
+
return {
|
|
232
|
+
...basic,
|
|
233
|
+
name: 'client-plugin',
|
|
234
|
+
description: 'Plugin with client-side code',
|
|
235
|
+
files: [
|
|
236
|
+
...basic.files,
|
|
237
|
+
{
|
|
238
|
+
path: 'plugins/{{name}}/client/index.ts',
|
|
239
|
+
content: `/**
|
|
240
|
+
* Client-side logic for {{pascalName}} plugin
|
|
241
|
+
*/
|
|
242
|
+
|
|
243
|
+
export class {{pascalName}}Client {
|
|
244
|
+
initialize() {
|
|
245
|
+
console.log(\`[{{name}}] Client initialized\`)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Add your client-side methods here
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export const {{camelName}}Client = new {{pascalName}}Client()
|
|
252
|
+
`
|
|
253
|
+
}
|
|
254
|
+
]
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private getFullTemplate(): Template {
|
|
259
|
+
const basic = this.getBasicTemplate()
|
|
260
|
+
const server = this.getServerOnlyTemplate()
|
|
261
|
+
const client = this.getClientOnlyTemplate()
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
...basic,
|
|
265
|
+
name: 'full-plugin',
|
|
266
|
+
description: 'Complete plugin with server and client code',
|
|
267
|
+
files: [
|
|
268
|
+
...basic.files,
|
|
269
|
+
...server.files.slice(basic.files.length), // Add server files
|
|
270
|
+
...client.files.slice(basic.files.length), // Add client files
|
|
271
|
+
{
|
|
272
|
+
path: 'plugins/{{name}}/types.ts',
|
|
273
|
+
content: `/**
|
|
274
|
+
* Type definitions for {{pascalName}} plugin
|
|
275
|
+
*/
|
|
276
|
+
|
|
277
|
+
export interface {{pascalName}}Config {
|
|
278
|
+
// Add your configuration types here
|
|
279
|
+
enabled: boolean
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export interface {{pascalName}}Options {
|
|
283
|
+
// Add your options types here
|
|
284
|
+
}
|
|
285
|
+
`
|
|
286
|
+
}
|
|
287
|
+
]
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
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
|
package/core/utils/version.ts
CHANGED
package/create-fluxstack.ts
CHANGED
|
@@ -5,16 +5,17 @@ import { resolve, join } from 'path'
|
|
|
5
5
|
import { existsSync, mkdirSync, cpSync, writeFileSync, readFileSync } from 'fs'
|
|
6
6
|
import chalk from 'chalk'
|
|
7
7
|
import ora from 'ora'
|
|
8
|
+
import { FLUXSTACK_VERSION } from './core/utils/version'
|
|
8
9
|
|
|
9
10
|
const logo = `
|
|
10
|
-
ā” āāāāāāā āā āā āā āā āā āāāāāāā āāāāāāāā āāāāā āāāāāā āā āā
|
|
11
|
-
āā āā āā āā āā āā āā āā āā āā āā āā āā
|
|
12
|
-
āāāāā āā āā āā āāā āāāāāāā āā āāāāāāā āā āāāāā
|
|
13
|
-
āā āā āā āā āā āā āā āā āā āā āā āā āā
|
|
14
|
-
āā āāāāāāā āāāāāā āā āā āāāāāāā āā āā āā āāāāāā āā āā
|
|
11
|
+
ā” āāāāāāā āā āā āā āā āā āāāāāāā āāāāāāāā āāāāā āāāāāā āā āā
|
|
12
|
+
āā āā āā āā āā āā āā āā āā āā āā āā āā
|
|
13
|
+
āāāāā āā āā āā āāā āāāāāāā āā āāāāāāā āā āāāāā
|
|
14
|
+
āā āā āā āā āā āā āā āā āā āā āā āā āā
|
|
15
|
+
āā āāāāāāā āāāāāā āā āā āāāāāāā āā āā āā āāāāāā āā āā
|
|
15
16
|
|
|
16
17
|
${chalk.cyan('š« Powered by Bun - The Divine Runtime ā”')}
|
|
17
|
-
${chalk.gray(
|
|
18
|
+
${chalk.gray(`FluxStack v${FLUXSTACK_VERSION} - Creates full-stack TypeScript apps`)}
|
|
18
19
|
`
|
|
19
20
|
|
|
20
21
|
program
|
|
@@ -58,6 +59,7 @@ program
|
|
|
58
59
|
'core',
|
|
59
60
|
'app',
|
|
60
61
|
'config', // ā
CRITICAL: Copy config folder with declarative configs
|
|
62
|
+
// 'plugins', // TODO: Copy when crypto-auth plugin is complete
|
|
61
63
|
'ai-context', // ā
CRITICAL: Copy AI documentation for users
|
|
62
64
|
'bun.lock', // ā
CRITICAL: Copy lockfile to maintain working versions
|
|
63
65
|
'package.json', // ā
Copy real package.json from framework
|
|
@@ -67,15 +69,122 @@ program
|
|
|
67
69
|
'CLAUDE.md', // ā
Project instructions for AI assistants
|
|
68
70
|
'README.md'
|
|
69
71
|
]
|
|
70
|
-
|
|
72
|
+
|
|
71
73
|
for (const file of filesToCopy) {
|
|
72
74
|
const sourcePath = join(frameworkDir, file)
|
|
73
75
|
const destPath = join(projectPath, file)
|
|
74
|
-
|
|
76
|
+
|
|
75
77
|
if (existsSync(sourcePath)) {
|
|
76
78
|
cpSync(sourcePath, destPath, { recursive: true })
|
|
77
79
|
}
|
|
78
80
|
}
|
|
81
|
+
|
|
82
|
+
// Create empty plugins directory for user plugins
|
|
83
|
+
const pluginsDir = join(projectPath, 'plugins')
|
|
84
|
+
mkdirSync(pluginsDir, { recursive: true })
|
|
85
|
+
|
|
86
|
+
// Create a README in plugins folder
|
|
87
|
+
const pluginsReadme = `# Plugins
|
|
88
|
+
|
|
89
|
+
This folder is for your custom FluxStack plugins.
|
|
90
|
+
|
|
91
|
+
## š Documentation
|
|
92
|
+
|
|
93
|
+
For complete plugin development guide, see:
|
|
94
|
+
- \`ai-context/development/plugins-guide.md\` - Full plugin documentation
|
|
95
|
+
- \`ai-context/examples/\` - Plugin examples
|
|
96
|
+
|
|
97
|
+
## š¦ Available CLI Commands
|
|
98
|
+
|
|
99
|
+
\`\`\`bash
|
|
100
|
+
# Create a new plugin
|
|
101
|
+
bun run cli make:plugin my-plugin # Basic plugin
|
|
102
|
+
bun run cli make:plugin my-plugin --template full # Full plugin (server + client)
|
|
103
|
+
bun run cli make:plugin my-plugin --template server # Server-only plugin
|
|
104
|
+
|
|
105
|
+
# Manage plugin dependencies
|
|
106
|
+
bun run cli plugin:deps install # Install plugin dependencies
|
|
107
|
+
bun run cli plugin:deps list # List plugin dependencies
|
|
108
|
+
bun run cli plugin:deps check # Check for conflicts
|
|
109
|
+
bun run cli plugin:deps clean # Clean unused dependencies
|
|
110
|
+
\`\`\`
|
|
111
|
+
|
|
112
|
+
## š Plugin Structure
|
|
113
|
+
|
|
114
|
+
\`\`\`
|
|
115
|
+
plugins/
|
|
116
|
+
āāā my-plugin/
|
|
117
|
+
ā āāā plugin.json # Plugin metadata (name, version, dependencies)
|
|
118
|
+
ā āāā index.ts # Plugin entry point (server-side hooks)
|
|
119
|
+
ā āāā server/ # Server-side code (optional)
|
|
120
|
+
ā āāā client/ # Client-side code (optional)
|
|
121
|
+
\`\`\`
|
|
122
|
+
|
|
123
|
+
## ā” Quick Start
|
|
124
|
+
|
|
125
|
+
1. Create your plugin folder: \`plugins/my-plugin/\`
|
|
126
|
+
2. Create \`plugin.json\` with metadata
|
|
127
|
+
3. Create \`index.ts\` with your plugin logic
|
|
128
|
+
4. Use \`bun run cli plugin:deps install\` if you need extra dependencies
|
|
129
|
+
|
|
130
|
+
## š Intercepting Requests
|
|
131
|
+
|
|
132
|
+
Plugins can intercept and modify requests using hooks:
|
|
133
|
+
|
|
134
|
+
\`\`\`typescript
|
|
135
|
+
// plugins/my-plugin/index.ts
|
|
136
|
+
import type { FluxStackPlugin, PluginContext } from '@/core/types/plugin'
|
|
137
|
+
|
|
138
|
+
export class MyPlugin implements FluxStackPlugin {
|
|
139
|
+
name = 'my-plugin'
|
|
140
|
+
version = '1.0.0'
|
|
141
|
+
|
|
142
|
+
// Intercept every request
|
|
143
|
+
async onRequest(context: PluginContext, request: Request): Promise<void> {
|
|
144
|
+
// Example: Add custom headers
|
|
145
|
+
const url = new URL(request.url)
|
|
146
|
+
console.log(\`[\${this.name}] Request to: \${url.pathname}\`)
|
|
147
|
+
|
|
148
|
+
// Example: Validate authentication
|
|
149
|
+
const token = request.headers.get('Authorization')
|
|
150
|
+
if (!token && url.pathname.startsWith('/api/protected')) {
|
|
151
|
+
throw new Error('Unauthorized')
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Intercept every response
|
|
156
|
+
async onResponse(context: PluginContext, response: Response): Promise<void> {
|
|
157
|
+
console.log(\`[\${this.name}] Response status: \${response.status}\`)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Handle errors
|
|
161
|
+
async onError(context: PluginContext, error: Error): Promise<void> {
|
|
162
|
+
console.error(\`[\${this.name}] Error:\`, error.message)
|
|
163
|
+
// Example: Send to error tracking service
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
\`\`\`
|
|
167
|
+
|
|
168
|
+
## š Available Hooks
|
|
169
|
+
|
|
170
|
+
- **\`setup\`**: Initialize plugin resources (called once at startup)
|
|
171
|
+
- **\`onServerStart\`**: Run when server starts
|
|
172
|
+
- **\`onRequest\`**: Intercept incoming requests (before route handlers)
|
|
173
|
+
- **\`onResponse\`**: Intercept outgoing responses (after route handlers)
|
|
174
|
+
- **\`onError\`**: Handle errors globally
|
|
175
|
+
|
|
176
|
+
## š” Common Use Cases
|
|
177
|
+
|
|
178
|
+
- **Authentication**: Validate tokens in \`onRequest\`
|
|
179
|
+
- **Logging**: Log requests/responses for analytics
|
|
180
|
+
- **Rate Limiting**: Track request counts per IP
|
|
181
|
+
- **CORS**: Add headers in \`onResponse\`
|
|
182
|
+
- **Request Transformation**: Modify request body/headers
|
|
183
|
+
- **Response Transformation**: Add custom headers, compress responses
|
|
184
|
+
|
|
185
|
+
See the documentation for detailed examples and best practices.
|
|
186
|
+
`
|
|
187
|
+
writeFileSync(join(pluginsDir, 'README.md'), pluginsReadme)
|
|
79
188
|
|
|
80
189
|
// Generate .gitignore using template (instead of copying)
|
|
81
190
|
const gitignoreContent = `# Dependencies
|