@bemoje/cli 0.3.0 β 1.0.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/LICENSE +21 -0
- package/README.md +358 -0
- package/package.json +26 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Benjamin MΓΈller Jensen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
# @bemoje/cli
|
|
2
|
+
|
|
3
|
+
A type-safe CLI builder focused on command composition and help generation without execution coupling. Parse arguments, generate help, and integrate with existing CLI frameworks through a clean, fluent API.
|
|
4
|
+
|
|
5
|
+
## Key Features
|
|
6
|
+
|
|
7
|
+
- **π― Composition-Focused**: Build command structures without execution logic - parse arguments and generate help only
|
|
8
|
+
- **π Type-Safe**: Full TypeScript support with type inference for arguments and options
|
|
9
|
+
- **π Focused API**: Streamlined interface designed specifically for parsing and help generation
|
|
10
|
+
- **π¨ Flexible Help**: Fork of commander.js Help class with enhanced API and adapter support
|
|
11
|
+
- **β
Validation**: Built-in CLI argument ordering validation and name conflict detection
|
|
12
|
+
- **π Commander.js Compatible**: Adapter allows using this Help system with existing commander.js commands
|
|
13
|
+
|
|
14
|
+
## Core Philosophy
|
|
15
|
+
|
|
16
|
+
While commander.js provides a comprehensive solution that combines command definition with action execution, `@bemoje/cli` takes a different approach by focusing purely on:
|
|
17
|
+
|
|
18
|
+
1. **Command Structure Definition** - Define arguments, options, and subcommands
|
|
19
|
+
2. **Argument Parsing** - Parse `process.argv` into structured data
|
|
20
|
+
3. **Help Generation** - Render formatted help text with customizable styling
|
|
21
|
+
|
|
22
|
+
This separation of concerns allows for architectures where CLI parsing logic is decoupled from business logic, making it ideal for scenarios where you want to handle argument parsing and action execution in separate layers.
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { Command } from '@bemoje/cli'
|
|
28
|
+
|
|
29
|
+
// Define command structure
|
|
30
|
+
const cmd = new Command('myapp')
|
|
31
|
+
.setVersion('1.0.0')
|
|
32
|
+
.setDescription('My awesome CLI application')
|
|
33
|
+
.argument('<input>', 'Input file path')
|
|
34
|
+
.argument('[output]', 'Output file path', { defaultValue: 'out.txt' })
|
|
35
|
+
.option('-v, --verbose', 'Enable verbose output')
|
|
36
|
+
.option('-f, --format <type>', 'Output format', { choices: ['json', 'xml', 'yaml'] })
|
|
37
|
+
|
|
38
|
+
// Parse command line arguments
|
|
39
|
+
const result = cmd.parse(process.argv.slice(2))
|
|
40
|
+
|
|
41
|
+
console.log('Arguments:', result.arguments) // ['input.txt', 'out.txt']
|
|
42
|
+
console.log('Options:', result.options) // { verbose: true, format: 'json' }
|
|
43
|
+
console.log('Command:', result.command.name) // 'myapp'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Command Definition
|
|
47
|
+
|
|
48
|
+
### Basic Setup
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
const cmd = new Command('myapp')
|
|
52
|
+
.setVersion('1.0.0')
|
|
53
|
+
.setDescription('Application description')
|
|
54
|
+
.setSummary('Short summary for help')
|
|
55
|
+
.setAliases(['app', 'my-app'])
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Arguments (Positional)
|
|
59
|
+
|
|
60
|
+
Arguments follow strict ordering rules: required β optional β variadic
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
// Required argument
|
|
64
|
+
cmd.argument('<input>', 'Input file path')
|
|
65
|
+
|
|
66
|
+
// Optional argument with default
|
|
67
|
+
cmd.argument('[output]', 'Output file path', { defaultValue: 'dist/output.txt' })
|
|
68
|
+
|
|
69
|
+
// Required variadic (multiple values)
|
|
70
|
+
cmd.argument('<files...>', 'Multiple input files')
|
|
71
|
+
|
|
72
|
+
// Optional variadic with defaults
|
|
73
|
+
cmd.argument('[patterns...]', 'Glob patterns', { defaultValue: ['**/*.js'] })
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Options (Named Parameters)
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
// Boolean flag
|
|
80
|
+
cmd.option('-v, --verbose', 'Enable verbose output')
|
|
81
|
+
|
|
82
|
+
// Required string option
|
|
83
|
+
cmd.option('-f, --format <type>', 'Output format')
|
|
84
|
+
|
|
85
|
+
// Optional string option with default
|
|
86
|
+
cmd.option('-o, --output [path]', 'Output directory', { defaultValue: 'dist' })
|
|
87
|
+
|
|
88
|
+
// Required variadic option
|
|
89
|
+
cmd.option('-i, --include <patterns...>', 'Include patterns')
|
|
90
|
+
|
|
91
|
+
// Optional variadic option with defaults
|
|
92
|
+
cmd.option('-e, --exclude [patterns...]', 'Exclude patterns', {
|
|
93
|
+
defaultValue: ['node_modules', '.git'],
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
// Option with choices and environment variable
|
|
97
|
+
cmd.option('-l, --log-level [level]', 'Log level', {
|
|
98
|
+
choices: ['error', 'warn', 'info', 'debug'],
|
|
99
|
+
defaultValue: 'info',
|
|
100
|
+
env: 'LOG_LEVEL',
|
|
101
|
+
})
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Global Options
|
|
105
|
+
|
|
106
|
+
Options defined on parent commands are available to subcommands:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
const app = new Command('myapp').option('-c, --config <file>', 'Config file')
|
|
110
|
+
|
|
111
|
+
app.subcommand('build').option('-w, --watch', 'Watch mode')
|
|
112
|
+
|
|
113
|
+
// Both --config and --watch are available to 'build' subcommand
|
|
114
|
+
const result = app.parse(['build', '--config', 'myconfig.json', '--watch'])
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Subcommands
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
const cmd = new Command('git')
|
|
121
|
+
|
|
122
|
+
// Create subcommand
|
|
123
|
+
const add = cmd
|
|
124
|
+
.subcommand('add')
|
|
125
|
+
.setDescription('Add files to staging area')
|
|
126
|
+
.argument('<files...>', 'Files to add')
|
|
127
|
+
.option('-A, --all', 'Add all files')
|
|
128
|
+
|
|
129
|
+
const commit = cmd
|
|
130
|
+
.subcommand('commit')
|
|
131
|
+
.setDescription('Create a commit')
|
|
132
|
+
.argument('[message]', 'Commit message')
|
|
133
|
+
.option('-m, --message <msg>', 'Commit message')
|
|
134
|
+
.option('-a, --all', 'Commit all changes')
|
|
135
|
+
|
|
136
|
+
// Parsing automatically routes to subcommands
|
|
137
|
+
const result = cmd.parse(['add', 'file1.js', 'file2.js', '-A'])
|
|
138
|
+
// result.command === add subcommand instance
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Parsing Results
|
|
142
|
+
|
|
143
|
+
The `parse()` method returns a structured result:
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
interface ParseResult {
|
|
147
|
+
command: Command // The command that was executed (handles subcommands)
|
|
148
|
+
arguments: unknown[] // Parsed positional arguments
|
|
149
|
+
options: Record<string, unknown> // Parsed options/flags
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Argument Types
|
|
154
|
+
|
|
155
|
+
- **Single values**: `string`
|
|
156
|
+
- **Optional with defaults**: `string` (default applied if not provided)
|
|
157
|
+
- **Variadic**: `string[]` (array of values)
|
|
158
|
+
|
|
159
|
+
### Option Types
|
|
160
|
+
|
|
161
|
+
- **Boolean flags**: `boolean`
|
|
162
|
+
- **String options**: `string`
|
|
163
|
+
- **Variadic options**: `string[]`
|
|
164
|
+
|
|
165
|
+
## Help System
|
|
166
|
+
|
|
167
|
+
### Rendering Help
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
import { Help } from '@bemoje/cli'
|
|
171
|
+
|
|
172
|
+
// Use default help formatting
|
|
173
|
+
console.log(cmd.renderHelp())
|
|
174
|
+
|
|
175
|
+
// Customize help configuration
|
|
176
|
+
const customHelp = new Help()
|
|
177
|
+
customHelp.helpWidth = 100
|
|
178
|
+
customHelp.sortOptions = true
|
|
179
|
+
customHelp.showGlobalOptions = true
|
|
180
|
+
|
|
181
|
+
console.log(cmd.renderHelp(customHelp))
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Help Configuration
|
|
185
|
+
|
|
186
|
+
Configure help behavior per command:
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
cmd.setHelpConfiguration({
|
|
190
|
+
sortOptions: true,
|
|
191
|
+
sortSubcommands: true,
|
|
192
|
+
showGlobalOptions: false,
|
|
193
|
+
helpWidth: 80,
|
|
194
|
+
})
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Custom Help Styling
|
|
198
|
+
|
|
199
|
+
It may be more convenient to extend the Help class for more extensive customization.
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
import { Help } from '@bemoje/cli'
|
|
203
|
+
|
|
204
|
+
class ColoredHelp extends Help {
|
|
205
|
+
styleTitle(str: string): string {
|
|
206
|
+
return `\x1b[1m${str}\x1b[0m` // Bold
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
styleOptionText(str: string): string {
|
|
210
|
+
return `\x1b[36m${str}\x1b[0m` // Cyan
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
styleArgumentText(str: string): string {
|
|
214
|
+
return `\x1b[33m${str}\x1b[0m` // Yellow
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
console.log(cmd.renderHelp(new ColoredHelp()))
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Commander.js Integration
|
|
222
|
+
|
|
223
|
+
Use the enhanced Help system with existing commander.js commands:
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
import { Command } from 'commander'
|
|
227
|
+
import { CommanderHelpAdapter } from '@bemoje/cli'
|
|
228
|
+
|
|
229
|
+
// Existing commander.js command
|
|
230
|
+
const commanderCmd = new Command('example')
|
|
231
|
+
.description('Example commander.js command')
|
|
232
|
+
.option('-v, --verbose', 'verbose output')
|
|
233
|
+
|
|
234
|
+
// Use enhanced help system
|
|
235
|
+
const adapter = new CommanderHelpAdapter(commanderCmd)
|
|
236
|
+
console.log(adapter.renderHelp())
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Custom Adapters
|
|
240
|
+
|
|
241
|
+
You can create your own adapters for any command system by implementing the `ICommandHelp` interface. This allows you to use the enhanced Help system with any CLI library or custom command structure.
|
|
242
|
+
|
|
243
|
+
### Creating Custom Adapters
|
|
244
|
+
|
|
245
|
+
**Example**: Adapter for a hypothetical CLI library:
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
import { ICommandHelp, IHelp, Help, renderHelp } from '@bemoje/cli'
|
|
249
|
+
|
|
250
|
+
class MyAdapter implements ICommandHelp {
|
|
251
|
+
constructor(private cmd: SomeOtherCliCommand) {}
|
|
252
|
+
|
|
253
|
+
// implement ICommandHelp interface methods
|
|
254
|
+
get name() {
|
|
255
|
+
return this.cmd.getName()
|
|
256
|
+
}
|
|
257
|
+
get commands() {
|
|
258
|
+
return this.cmd.getSubcommands().map((sub) => {
|
|
259
|
+
return new MyAdapter(sub)
|
|
260
|
+
})
|
|
261
|
+
}
|
|
262
|
+
get options() {
|
|
263
|
+
return this.cmd.getOptions().map((opt) => ({
|
|
264
|
+
flags: opt.flags,
|
|
265
|
+
//... map props
|
|
266
|
+
}))
|
|
267
|
+
}
|
|
268
|
+
// etc...
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const myCommand = new SomeOtherCliCommand('myapp')
|
|
272
|
+
const adapter = new MyAdapter(myCommand)
|
|
273
|
+
console.log(renderHelp(adapter, new Help()))
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
This pattern allows you to:
|
|
277
|
+
|
|
278
|
+
- **Bridge Different CLI Libraries**: Use the enhanced Help system with any command framework
|
|
279
|
+
- **Migrate Gradually**: Introduce better help formatting without rewriting existing commands
|
|
280
|
+
- **Standardize Help Output**: Consistent help formatting across different CLI tools in your project
|
|
281
|
+
- **Extend Legacy Systems**: Add modern help features to older CLI codebases
|
|
282
|
+
|
|
283
|
+
## Validation
|
|
284
|
+
|
|
285
|
+
Commands automatically validate:
|
|
286
|
+
|
|
287
|
+
- Argument ordering (required before optional before variadic)
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
cmd.argument('[optional]', 'Optional arg').argument('<required>', 'Required arg')
|
|
291
|
+
//=> β Error!
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
- Unique option names and short flags, including globals across parent/child commands
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
cmd.option('-v, --verbose', 'Verbose output').option('-v, --video', 'Video mode')
|
|
298
|
+
//=> β Error!
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
- Single variadic argument per command
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
cmd.argument('<files...>', 'First variadic').argument('<more...>', 'Second variadic')
|
|
305
|
+
//=> β Error!
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## State Management
|
|
309
|
+
|
|
310
|
+
Commands can be serialized and restored:
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
// Serialize command configuration
|
|
314
|
+
const state = cmd.toJSON()
|
|
315
|
+
|
|
316
|
+
// Restore from state
|
|
317
|
+
const restoredCmd = new Command('temp')
|
|
318
|
+
restoredCmd.setState(state)
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## Command Class
|
|
322
|
+
|
|
323
|
+
**Constructor**:
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
- new Command(name: string, parent?: Command)
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
**Structure Methods**:
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
- argument(usage, description, options?): this
|
|
333
|
+
- option(usage, description, options?): this
|
|
334
|
+
- subcommand(name: string): Command
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
**Configuration Methods**:
|
|
338
|
+
|
|
339
|
+
```ts
|
|
340
|
+
- setVersion(version?: string): this
|
|
341
|
+
- setName(name: string): this
|
|
342
|
+
- setDescription(...lines: string[]): this
|
|
343
|
+
- setSummary(summary?: string): this
|
|
344
|
+
- setHidden(hidden?: boolean): this
|
|
345
|
+
- setGroup(group?: string): this
|
|
346
|
+
- setHelpConfiguration(config?: Partial<IHelp>): this
|
|
347
|
+
- extendHelpConfiguration(config: Partial<IHelp>): this
|
|
348
|
+
- setAliases(...aliases: (string | string[])[]): this
|
|
349
|
+
- addAliases(...aliases: (string | string[])[]): this
|
|
350
|
+
- setParent(parent: Command | null): this
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
**Parsing & Help**:
|
|
354
|
+
|
|
355
|
+
```ts
|
|
356
|
+
- parse(argv?: string[], globalOptions?: OptionDescriptor[]): ParseResult
|
|
357
|
+
- renderHelp(help?: IHelp): string
|
|
358
|
+
```
|
package/package.json
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bemoje/cli",
|
|
3
3
|
"description": "A type-safe CLI builder focused on command composition and help generation without execution coupling. Parse arguments, generate help, and integrate with existing CLI frameworks through a clean, fluent API.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "1.0.1",
|
|
5
5
|
"private": false,
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"type": "module",
|
|
8
|
-
"module": "index.js",
|
|
8
|
+
"module": "./index.js",
|
|
9
|
+
"main": "./index.js",
|
|
9
10
|
"exports": {
|
|
10
11
|
".": "./lib/index.js",
|
|
11
12
|
"./*": "./lib/*.js",
|
|
12
13
|
"./package.json": "./package.json",
|
|
13
14
|
"./index.d.ts": "./index.d.ts"
|
|
14
15
|
},
|
|
15
|
-
"typings": "index.d.ts",
|
|
16
|
+
"typings": "./index.d.ts",
|
|
16
17
|
"dependencies": {},
|
|
17
18
|
"devDependencies": {
|
|
18
19
|
"commander": "^14.0.0"
|
|
@@ -23,6 +24,26 @@
|
|
|
23
24
|
"url": "git+https://github.com/bemoje/mono.git"
|
|
24
25
|
},
|
|
25
26
|
"keywords": [
|
|
27
|
+
"cli",
|
|
28
|
+
"command-line",
|
|
29
|
+
"argument-parser",
|
|
30
|
+
"options-parser",
|
|
31
|
+
"typescript",
|
|
32
|
+
"type-safe",
|
|
33
|
+
"fluent-api",
|
|
34
|
+
"help-generation",
|
|
35
|
+
"commander",
|
|
36
|
+
"commander.js",
|
|
37
|
+
"subcommands",
|
|
38
|
+
"validation",
|
|
39
|
+
"parsing",
|
|
40
|
+
"composition",
|
|
41
|
+
"adapter",
|
|
42
|
+
"terminal",
|
|
43
|
+
"console",
|
|
44
|
+
"args",
|
|
45
|
+
"argv",
|
|
46
|
+
"bemoje",
|
|
26
47
|
"cli"
|
|
27
48
|
],
|
|
28
49
|
"author": {
|
|
@@ -30,5 +51,5 @@
|
|
|
30
51
|
"email": "bemoje@bemoje.net",
|
|
31
52
|
"url": "https://github.com/bemoje/"
|
|
32
53
|
},
|
|
33
|
-
"homepage": "https://github.com/bemoje/mono"
|
|
34
|
-
}
|
|
54
|
+
"homepage": "https://github.com/bemoje/mono/tree/main/libs/cli"
|
|
55
|
+
}
|