@wooksjs/event-cli 0.6.6 → 0.7.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/README.md +10 -34
- package/dist/index.cjs +59 -41
- package/dist/index.d.ts +23 -25
- package/dist/index.mjs +40 -35
- package/package.json +7 -7
- package/skills/wooksjs-event-cli/SKILL.md +15 -19
- package/skills/wooksjs-event-cli/commands.md +109 -428
- package/skills/wooksjs-event-cli/core.md +77 -294
- package/skills/wooksjs-event-cli/event-core.md +0 -562
|
@@ -1,353 +1,136 @@
|
|
|
1
|
-
# Core
|
|
1
|
+
# Core setup & routing — @wooksjs/event-cli
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Creating a CLI app, registering commands, running them, and architecture overview.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Concepts
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
`@wooksjs/event-cli` wraps command-line argument parsing with Wooks' composable architecture. Each command invocation gets its own `EventContext`, and handlers are plain functions. Command routing uses the same `@prostojs/router` as the HTTP adapter — supporting parameters, optional params, wildcards, and aliases.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Key principles:
|
|
12
|
-
1. **Commands are routes** — CLI command paths use the same `@prostojs/router` as HTTP routes, with params (`:name`), optional segments (`:arg?`), and wildcards.
|
|
13
|
-
2. **Flags are composable** — Call `useCliOptions()` or `useCliOption('verbose')` from anywhere in the handler chain to get parsed flags.
|
|
14
|
-
3. **Built-in help generation** — Register descriptions, options, args, examples, and aliases alongside your commands. The framework generates formatted help text.
|
|
9
|
+
Arguments are parsed with `minimist`. Help text is generated automatically from command metadata via `@prostojs/cli-help`.
|
|
15
10
|
|
|
16
11
|
## Installation
|
|
17
12
|
|
|
18
13
|
```bash
|
|
19
|
-
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## Creating a CLI App
|
|
23
|
-
|
|
24
|
-
```ts
|
|
25
|
-
import { createCliApp } from '@wooksjs/event-cli'
|
|
26
|
-
|
|
27
|
-
const app = createCliApp()
|
|
28
|
-
|
|
29
|
-
app.cli('hello', () => 'Hello World!')
|
|
30
|
-
|
|
31
|
-
app.run()
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
`createCliApp(opts?, wooks?)` returns a `WooksCli` instance. Options:
|
|
35
|
-
|
|
36
|
-
```ts
|
|
37
|
-
interface TWooksCliOptions {
|
|
38
|
-
onError?: (e: Error) => void // custom error handler
|
|
39
|
-
onNotFound?: TWooksHandler // custom not-found handler
|
|
40
|
-
onUnknownCommand?: (params: string[], raiseError: () => void) => unknown
|
|
41
|
-
logger?: TConsoleBase // custom logger
|
|
42
|
-
eventOptions?: TEventOptions // event-level logger config
|
|
43
|
-
cliHelp?: TCliHelpRenderer | TCliHelpOptions // help renderer or options
|
|
44
|
-
router?: {
|
|
45
|
-
ignoreTrailingSlash?: boolean
|
|
46
|
-
ignoreCase?: boolean
|
|
47
|
-
cacheLimit?: number
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## Running Commands
|
|
53
|
-
|
|
54
|
-
### `app.run(argv?, opts?)`
|
|
55
|
-
|
|
56
|
-
Starts command processing. By default reads `process.argv.slice(2)`:
|
|
57
|
-
|
|
58
|
-
```ts
|
|
59
|
-
// Default: reads from process.argv
|
|
60
|
-
app.run()
|
|
61
|
-
|
|
62
|
-
// Override argv for testing or programmatic use
|
|
63
|
-
await app.run(['greet', 'Alice', '--verbose'])
|
|
64
|
-
|
|
65
|
-
// Pass minimist options to control flag parsing
|
|
66
|
-
await app.run(['cmd', '-cA'], { boolean: ['A'] })
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
**How `run()` works:**
|
|
70
|
-
|
|
71
|
-
1. Parses `argv` with `minimist` to separate positional args from flags.
|
|
72
|
-
2. Builds a route path from positional args: `['greet', 'Alice']` becomes `/greet/Alice`.
|
|
73
|
-
3. Creates a CLI event context with the parsed data.
|
|
74
|
-
4. Looks up the matching command handler via the router.
|
|
75
|
-
5. Executes the handler and processes the return value.
|
|
76
|
-
|
|
77
|
-
### Return value handling
|
|
78
|
-
|
|
79
|
-
The handler's return value is automatically output:
|
|
80
|
-
|
|
81
|
-
```ts
|
|
82
|
-
// String → console.log as-is
|
|
83
|
-
app.cli('text', () => 'plain text')
|
|
84
|
-
|
|
85
|
-
// Object → JSON.stringify with indentation
|
|
86
|
-
app.cli('json', () => ({ key: 'value' }))
|
|
87
|
-
|
|
88
|
-
// Array → each element logged separately (strings as-is, objects as JSON)
|
|
89
|
-
app.cli('list', () => ['line1', 'line2', { data: true }])
|
|
90
|
-
|
|
91
|
-
// undefined → no output
|
|
92
|
-
app.cli('silent', () => { /* side effects only */ })
|
|
93
|
-
|
|
94
|
-
// Error → triggers onError handler
|
|
95
|
-
app.cli('fail', () => new Error('something broke'))
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
## How CLI Context Works
|
|
99
|
-
|
|
100
|
-
When `run()` is called, the adapter creates a CLI-specific event context:
|
|
101
|
-
|
|
102
|
-
```
|
|
103
|
-
argv arrives
|
|
104
|
-
→ minimist parses flags
|
|
105
|
-
→ createCliContext({ argv, pathParams, cliHelp, command }, options)
|
|
106
|
-
→ AsyncLocalStorage.run(cliContextStore, handler)
|
|
107
|
-
→ router matches command path → handler runs
|
|
108
|
-
→ handler calls useCliOptions(), useCliHelp(), etc.
|
|
109
|
-
→ each composable calls useCliContext()
|
|
110
|
-
→ reads/writes the CLI context store via init(), get(), set()
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
### The CLI Context Store
|
|
114
|
-
|
|
115
|
-
The CLI adapter extends the base event context with:
|
|
116
|
-
|
|
117
|
-
```ts
|
|
118
|
-
interface TCliContextStore {
|
|
119
|
-
flags?: Record<string, boolean | string> // parsed minimist flags
|
|
120
|
-
}
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
The `event` section contains CLI-specific data:
|
|
124
|
-
|
|
125
|
-
```ts
|
|
126
|
-
interface TCliEventData {
|
|
127
|
-
argv: string[] // raw argv array
|
|
128
|
-
pathParams: string[] // positional args (non-flag)
|
|
129
|
-
command: string // matched command path
|
|
130
|
-
opts?: minimist.Opts // minimist options passed to run()
|
|
131
|
-
type: 'CLI' // event type identifier
|
|
132
|
-
cliHelp: TCliHelpRenderer // help renderer instance
|
|
133
|
-
}
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
### Extending the CLI Store for Custom Composables
|
|
137
|
-
|
|
138
|
-
Create custom composables for CLI by extending the store type via the generic parameter on `useCliContext`:
|
|
139
|
-
|
|
140
|
-
```ts
|
|
141
|
-
import { useCliContext } from '@wooksjs/event-cli'
|
|
142
|
-
|
|
143
|
-
interface TMyStore {
|
|
144
|
-
config?: {
|
|
145
|
-
loaded?: Record<string, unknown> | null
|
|
146
|
-
configPath?: string
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export function useConfig() {
|
|
151
|
-
const { store } = useCliContext<TMyStore>()
|
|
152
|
-
const { init } = store('config')
|
|
153
|
-
|
|
154
|
-
const loadConfig = () =>
|
|
155
|
-
init('loaded', () => {
|
|
156
|
-
const flags = useCliOptions()
|
|
157
|
-
const configPath = flags.config as string || './config.json'
|
|
158
|
-
return JSON.parse(fs.readFileSync(configPath, 'utf-8'))
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
return { loadConfig }
|
|
162
|
-
}
|
|
14
|
+
pnpm add @wooksjs/event-cli
|
|
163
15
|
```
|
|
164
16
|
|
|
165
|
-
|
|
17
|
+
Peer dependencies: `@wooksjs/event-core`, `wooks`, `@prostojs/router`, `@prostojs/logger`.
|
|
166
18
|
|
|
167
|
-
##
|
|
19
|
+
## API Reference
|
|
168
20
|
|
|
169
|
-
###
|
|
21
|
+
### `createCliApp(opts?): WooksCli`
|
|
170
22
|
|
|
171
|
-
|
|
23
|
+
Creates a CLI application instance.
|
|
172
24
|
|
|
173
25
|
```ts
|
|
174
|
-
|
|
175
|
-
throw new Error('Deployment failed')
|
|
176
|
-
})
|
|
177
|
-
// Output: ERROR: Deployment failed
|
|
178
|
-
// Process exits with code 1
|
|
179
|
-
```
|
|
26
|
+
import { createCliApp } from '@wooksjs/event-cli'
|
|
180
27
|
|
|
181
|
-
|
|
28
|
+
const app = createCliApp()
|
|
182
29
|
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
console.error(`[FATAL] ${error.message}`)
|
|
187
|
-
process.exit(2)
|
|
188
|
-
},
|
|
30
|
+
app.cli('greet/:name', () => {
|
|
31
|
+
const { params } = useRouteParams<{ name: string }>()
|
|
32
|
+
return `Hello, ${params.name}!`
|
|
189
33
|
})
|
|
190
|
-
```
|
|
191
34
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
Handlers can also return `Error` objects (not throw). The adapter passes them to `onError`:
|
|
195
|
-
|
|
196
|
-
```ts
|
|
197
|
-
app.cli('validate', () => {
|
|
198
|
-
if (!isValid()) return new Error('Validation failed')
|
|
199
|
-
return 'OK'
|
|
200
|
-
})
|
|
35
|
+
app.run() // processes process.argv
|
|
201
36
|
```
|
|
202
37
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
When no route matches the provided command:
|
|
206
|
-
|
|
207
|
-
### Default behavior
|
|
38
|
+
**Options (`TWooksCliOptions`):**
|
|
208
39
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
console.log(`Did you mean something else?`)
|
|
218
|
-
// Call raiseError() to use the default error behavior
|
|
219
|
-
raiseError()
|
|
220
|
-
},
|
|
221
|
-
})
|
|
222
|
-
```
|
|
40
|
+
| Option | Type | Description |
|
|
41
|
+
| ------------------ | ------------------------------------- | --------------------------------------------------------- |
|
|
42
|
+
| `logger` | `TConsoleBase` | Custom logger |
|
|
43
|
+
| `onError` | `(e: Error) => void` | Custom error handler (default: print + `process.exit(1)`) |
|
|
44
|
+
| `onNotFound` | `TWooksHandler` | Handler for unknown commands |
|
|
45
|
+
| `onUnknownCommand` | `(params, raiseError) => unknown` | Callback before "unknown command" error |
|
|
46
|
+
| `cliHelp` | `TCliHelpRenderer \| TCliHelpOptions` | Help renderer or options |
|
|
47
|
+
| `router` | router options | Custom router configuration |
|
|
223
48
|
|
|
224
|
-
###
|
|
49
|
+
### `app.cli(path, handler | options)`
|
|
225
50
|
|
|
226
|
-
|
|
51
|
+
Register a CLI command. Path segments can use `/` or space as separators.
|
|
227
52
|
|
|
228
53
|
```ts
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
useCommandLookupHelp() // throws with suggestions if found
|
|
234
|
-
raiseError() // fallback
|
|
235
|
-
},
|
|
54
|
+
// Simple handler
|
|
55
|
+
app.cli('build/:target', () => {
|
|
56
|
+
const { params } = useRouteParams<{ target: string }>()
|
|
57
|
+
return `Building ${params.target}...`
|
|
236
58
|
})
|
|
237
|
-
```
|
|
238
59
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
$ mycli deploy staging
|
|
243
|
-
$ mycli deploy production
|
|
60
|
+
// Equivalent paths:
|
|
61
|
+
app.cli('deploy staging', handler)
|
|
62
|
+
app.cli('deploy/staging', handler)
|
|
244
63
|
```
|
|
245
64
|
|
|
246
|
-
|
|
65
|
+
### `app.run(argv?, minimistOpts?)`
|
|
247
66
|
|
|
248
|
-
|
|
67
|
+
Start command processing. Defaults to `process.argv.slice(2)`.
|
|
249
68
|
|
|
250
69
|
```ts
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
console.log('Command not recognized. Run --help for usage.')
|
|
254
|
-
process.exit(1)
|
|
255
|
-
},
|
|
256
|
-
})
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
## Sharing Router Between Adapters
|
|
70
|
+
// Use process.argv
|
|
71
|
+
await app.run()
|
|
260
72
|
|
|
261
|
-
|
|
73
|
+
// Override argv
|
|
74
|
+
await app.run(['build', 'production', '--verbose'])
|
|
262
75
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
import { createCliApp } from '@wooksjs/event-cli'
|
|
266
|
-
|
|
267
|
-
const wooks = new Wooks()
|
|
268
|
-
const app1 = createCliApp({}, wooks)
|
|
269
|
-
const app2 = createCliApp({}, wooks) // shares the same routes
|
|
76
|
+
// With minimist options
|
|
77
|
+
await app.run(['cmd', '-cA'], { boolean: ['A'] })
|
|
270
78
|
```
|
|
271
79
|
|
|
272
|
-
|
|
80
|
+
Returns the handler's return value, or an `Error` if the command fails.
|
|
273
81
|
|
|
274
|
-
|
|
275
|
-
const app1 = createCliApp()
|
|
276
|
-
const app2 = createCliApp({}, app1) // shares app1's router
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
## Testing
|
|
82
|
+
## Command routing
|
|
280
83
|
|
|
281
|
-
|
|
84
|
+
Routes use the same `@prostojs/router` as HTTP. Command path segments become route segments:
|
|
282
85
|
|
|
283
86
|
```ts
|
|
284
|
-
|
|
285
|
-
|
|
87
|
+
// Static command
|
|
88
|
+
app.cli('init', handler) // $ mycli init
|
|
286
89
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
})
|
|
90
|
+
// With parameter
|
|
91
|
+
app.cli('build/:target', handler) // $ mycli build production
|
|
290
92
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
})
|
|
93
|
+
// Optional parameter
|
|
94
|
+
app.cli('test/:suite?', handler) // $ mycli test (suite = undefined)
|
|
95
|
+
// $ mycli test unit (suite = 'unit')
|
|
295
96
|
|
|
296
|
-
//
|
|
297
|
-
|
|
298
|
-
// Handler outputs: "Hello, Alice!"
|
|
97
|
+
// Multi-segment
|
|
98
|
+
app.cli('deploy staging setup', handler) // $ mycli deploy staging setup
|
|
299
99
|
```
|
|
300
100
|
|
|
301
|
-
|
|
101
|
+
Route params accessed via `useRouteParams()`:
|
|
302
102
|
|
|
303
103
|
```ts
|
|
304
|
-
|
|
305
|
-
const flags = useCliOptions()
|
|
306
|
-
return flags.env as string || 'development'
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
await app.run(['deploy', '--env', 'production'])
|
|
310
|
-
// Output: "production"
|
|
311
|
-
|
|
312
|
-
// With minimist options for boolean flags:
|
|
313
|
-
await app.run(['deploy', '-v'], { boolean: ['v'] })
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
## Logging
|
|
317
|
-
|
|
318
|
-
Get a scoped logger from the app:
|
|
104
|
+
import { useRouteParams } from '@wooksjs/event-cli'
|
|
319
105
|
|
|
320
|
-
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
106
|
+
app.cli('generate/:type/:name', () => {
|
|
107
|
+
const { params } = useRouteParams<{ type: string; name: string }>()
|
|
108
|
+
return `Generating ${params.type}: ${params.name}`
|
|
109
|
+
})
|
|
324
110
|
```
|
|
325
111
|
|
|
326
|
-
|
|
112
|
+
## Response handling
|
|
327
113
|
|
|
328
|
-
|
|
329
|
-
import { useEventLogger } from '@wooksjs/event-core'
|
|
114
|
+
Handler return values are printed to stdout:
|
|
330
115
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
116
|
+
| Return type | Behavior |
|
|
117
|
+
| ----------- | ------------------------------------------------- |
|
|
118
|
+
| `string` | `console.log(value)` |
|
|
119
|
+
| `array` | Each item logged (strings as-is, objects as JSON) |
|
|
120
|
+
| `object` | `console.log(JSON.stringify(value, null, ' '))` |
|
|
121
|
+
| `Error` | Passed to `onError` handler |
|
|
122
|
+
| `undefined` | Nothing printed |
|
|
337
123
|
|
|
338
124
|
## Best Practices
|
|
339
125
|
|
|
340
|
-
-
|
|
341
|
-
-
|
|
342
|
-
-
|
|
343
|
-
-
|
|
344
|
-
- **Return values instead of `console.log`** — The framework handles output formatting. Returning lets you test handlers more easily.
|
|
126
|
+
- Use `useRouteParams()` for positional arguments and `useCliOptions()` for flags
|
|
127
|
+
- Prefer the options object form of `app.cli()` to get auto-generated help
|
|
128
|
+
- Use `onUnknownCommand` with `useCommandLookupHelp()` for "did you mean?" suggestions
|
|
129
|
+
- Command paths with spaces and `/` are equivalent — use whichever is more readable
|
|
345
130
|
|
|
346
131
|
## Gotchas
|
|
347
132
|
|
|
348
|
-
-
|
|
349
|
-
-
|
|
350
|
-
-
|
|
351
|
-
-
|
|
352
|
-
- **Positional args after flags** — minimist's `_` array contains all positional arguments, including the command segments themselves. Use `useRouteParams()` to access command arguments, not raw `_`.
|
|
353
|
-
- **URI encoding** — Command segments containing `/` are automatically URI-encoded so they don't create extra path segments.
|
|
133
|
+
- `app.run()` is async — always `await` it
|
|
134
|
+
- Default error handling calls `process.exit(1)` — override with `onError` for testing
|
|
135
|
+
- Colons in command names need escaping: `app.cli('use\\:dev', handler)` for `$ mycli use:dev`
|
|
136
|
+
- `minimist` treats `--no-flag` as `flag: false` when the flag is in `boolean` array
|