@guanghechen/commander 1.0.12 → 2.1.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 +19 -101
- package/LICENSE +1 -1
- package/README.md +117 -205
- package/lib/cjs/index.cjs +1032 -143
- package/lib/esm/index.mjs +1010 -131
- package/lib/types/index.d.ts +218 -119
- package/package.json +18 -19
package/CHANGELOG.md
CHANGED
|
@@ -1,115 +1,33 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
-
All notable changes to this project will be documented in this file.
|
|
4
|
-
|
|
3
|
+
All notable changes to this project will be documented in this file. See
|
|
4
|
+
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
-
##
|
|
6
|
+
## 2.1.0 (2025-02-08)
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
* :wrench: chore: upgrade dependencies & update the node requirement from ([8ddfde1](https://github.com/guanghechen/node-scaffolds/commit/8ddfde1))
|
|
8
|
+
### Features
|
|
10
9
|
|
|
10
|
+
- Add `--write` option to `CompletionCommand` for direct file output
|
|
11
|
+
- Add `help` subcommand support for commands with subcommands
|
|
12
|
+
- Detect `--help`/`--version` before parsing to avoid required argument errors
|
|
13
|
+
- Add `#normalizeArgv()` preprocessing to simplify `--no-*` option handling
|
|
14
|
+
- Add `implements ICommand` for explicit interface implementation
|
|
11
15
|
|
|
16
|
+
## 2.0.1 (2025-02-07)
|
|
12
17
|
|
|
18
|
+
### Documentation
|
|
13
19
|
|
|
20
|
+
- Update README.md
|
|
14
21
|
|
|
15
|
-
|
|
22
|
+
### Miscellaneous
|
|
16
23
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
* chore: upgrade dependencies ([a5b7a87](https://github.com/guanghechen/node-scaffolds/commit/a5b7a87))
|
|
24
|
+
- Add LICENSE file
|
|
25
|
+
- Clean up build configs and standardize package exports
|
|
26
|
+
- Migrate from lerna to changesets
|
|
21
27
|
|
|
28
|
+
## 2.0.0 (2025-01-15)
|
|
22
29
|
|
|
30
|
+
### Features
|
|
23
31
|
|
|
32
|
+
- Initial stable release: A minimal, type-safe command-line interface builder with fluent API
|
|
24
33
|
|
|
25
|
-
|
|
26
|
-
## <small>1.0.10 (2025-03-30)</small>
|
|
27
|
-
|
|
28
|
-
* chore: upgrade dependencies ([a5b7a87](https://github.com/guanghechen/node-scaffolds/commit/a5b7a87))
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
## <small>1.0.9 (2025-02-26)</small>
|
|
35
|
-
|
|
36
|
-
* :bookmark: release ([9d47297](https://github.com/guanghechen/node-scaffolds/commit/9d47297))
|
|
37
|
-
* chore: sharp dependencies ([914ba6f](https://github.com/guanghechen/node-scaffolds/commit/914ba6f))
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
## <small>1.0.8 (2025-02-26)</small>
|
|
44
|
-
|
|
45
|
-
* chore: sharp dependencies ([914ba6f](https://github.com/guanghechen/node-scaffolds/commit/914ba6f))
|
|
46
|
-
* chore: upgrade devDependencies ([ca5de6d](https://github.com/guanghechen/node-scaffolds/commit/ca5de6d))
|
|
47
|
-
* :bookmark: release ([6fe5681](https://github.com/guanghechen/node-scaffolds/commit/6fe5681))
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
## <small>1.0.7 (2025-02-07)</small>
|
|
54
|
-
|
|
55
|
-
* chore: upgrade devDependencies ([ca5de6d](https://github.com/guanghechen/node-scaffolds/commit/ca5de6d))
|
|
56
|
-
* :arrow_up: chore: upgrade dependencies & no longer rely on globby since it breaking build ([b5d0fdd](https://github.com/guanghechen/node-scaffolds/commit/b5d0fdd))
|
|
57
|
-
* :bookmark: release ([6524942](https://github.com/guanghechen/node-scaffolds/commit/6524942))
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
## <small>1.0.6 (2025-01-09)</small>
|
|
64
|
-
|
|
65
|
-
* :arrow_up: chore: upgrade dependencies & no longer rely on globby since it breaking build ([b5d0fdd](https://github.com/guanghechen/node-scaffolds/commit/b5d0fdd))
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
## <small>1.0.5 (2024-10-03)</small>
|
|
72
|
-
|
|
73
|
-
* :arrow_up: chore: upgrade dependencies ([ba63623](https://github.com/guanghechen/node-scaffolds/commit/ba63623))
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
## <small>1.0.4 (2024-10-03)</small>
|
|
80
|
-
|
|
81
|
-
* :arrow_up: chore: upgrade dependencies ([e41de75](https://github.com/guanghechen/node-scaffolds/commit/e41de75))
|
|
82
|
-
* :wrench: chore: don't gen sourcemap in production env ([f7075d1](https://github.com/guanghechen/node-scaffolds/commit/f7075d1))
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
## <small>1.0.3 (2024-09-29)</small>
|
|
89
|
-
|
|
90
|
-
**Note:** Version bump only for package @guanghechen/commander
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
## <small>1.0.2 (2024-09-29)</small>
|
|
97
|
-
|
|
98
|
-
* :art: improve(commander): remove unnecessary third-party dependencies ([d5cb553](https://github.com/guanghechen/node-scaffolds/commit/d5cb553))
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
## <small>1.0.1 (2024-09-28)</small>
|
|
105
|
-
|
|
106
|
-
* :art: improve: reuse @guanghechen/cli ([6c8e6a0](https://github.com/guanghechen/node-scaffolds/commit/6c8e6a0))
|
|
107
|
-
* :wrench: chore: fix publish issues ([7316526](https://github.com/guanghechen/node-scaffolds/commit/7316526))
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
## <small>1.0.1 (2024-09-28)</small>
|
|
114
|
-
|
|
115
|
-
* :art: improve: reuse @guanghechen/cli ([6c8e6a0](https://github.com/guanghechen/node-scaffolds/commit/6c8e6a0))
|
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2023-present guanghechen (https://github.com/guanghechen)
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<header>
|
|
2
2
|
<h1 align="center">
|
|
3
|
-
<a href="https://github.com/guanghechen/
|
|
3
|
+
<a href="https://github.com/guanghechen/sora/tree/@guanghechen/commander@1.0.0/packages/commander#readme">@guanghechen/commander</a>
|
|
4
4
|
</h1>
|
|
5
5
|
<div align="center">
|
|
6
6
|
<a href="https://www.npmjs.com/package/@guanghechen/commander">
|
|
@@ -33,12 +33,6 @@
|
|
|
33
33
|
src="https://img.shields.io/node/v/@guanghechen/commander"
|
|
34
34
|
/>
|
|
35
35
|
</a>
|
|
36
|
-
<a href="https://github.com/facebook/jest">
|
|
37
|
-
<img
|
|
38
|
-
alt="ESLint Version"
|
|
39
|
-
src="https://img.shields.io/npm/dependency-version/@guanghechen/commander/peer/jest"
|
|
40
|
-
/>
|
|
41
|
-
</a>
|
|
42
36
|
<a href="https://github.com/facebook/jest">
|
|
43
37
|
<img
|
|
44
38
|
alt="Tested with Jest"
|
|
@@ -55,247 +49,165 @@
|
|
|
55
49
|
</header>
|
|
56
50
|
<br/>
|
|
57
51
|
|
|
58
|
-
|
|
52
|
+
A minimal, type-safe command-line interface builder with fluent API. Supports subcommands, option
|
|
53
|
+
parsing, shell completion generation (bash, fish, pwsh), and built-in help/version handling.
|
|
59
54
|
|
|
60
55
|
## Install
|
|
61
56
|
|
|
62
|
-
|
|
57
|
+
- npm
|
|
63
58
|
|
|
64
59
|
```bash
|
|
65
|
-
npm install --save
|
|
60
|
+
npm install --save @guanghechen/commander
|
|
66
61
|
```
|
|
67
62
|
|
|
68
|
-
|
|
63
|
+
- yarn
|
|
69
64
|
|
|
70
65
|
```bash
|
|
71
|
-
yarn add
|
|
66
|
+
yarn add @guanghechen/commander
|
|
72
67
|
```
|
|
73
68
|
|
|
74
69
|
## Usage
|
|
75
70
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
### Enhanced Command Class
|
|
79
|
-
|
|
80
|
-
```typescript
|
|
81
|
-
import { Command, type ICommandActionCallback } from '@guanghechen/commander'
|
|
82
|
-
|
|
83
|
-
interface IMyOptions extends ICommandConfigurationOptions {
|
|
84
|
-
readonly input: string
|
|
85
|
-
readonly output: string
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const command = new Command()
|
|
89
|
-
.name('my-tool')
|
|
90
|
-
.description('My awesome CLI tool')
|
|
91
|
-
.action<IMyOptions>((args, options, extra, self) => {
|
|
92
|
-
console.log('Arguments:', args)
|
|
93
|
-
console.log('Options:', options)
|
|
94
|
-
console.log('Extra args:', extra)
|
|
95
|
-
})
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
### Creating Top-Level Commands
|
|
71
|
+
### Basic Command
|
|
99
72
|
|
|
100
73
|
```typescript
|
|
101
|
-
import {
|
|
74
|
+
import { Command } from '@guanghechen/commander'
|
|
102
75
|
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
### Main Command Utilities
|
|
111
|
-
|
|
112
|
-
```typescript
|
|
113
|
-
import {
|
|
114
|
-
createMainCommandMounter,
|
|
115
|
-
createMainCommandExecutor,
|
|
116
|
-
type IMainCommandProcessor,
|
|
117
|
-
type IMainCommandCreator
|
|
118
|
-
} from '@guanghechen/commander'
|
|
119
|
-
|
|
120
|
-
// Define your main command processor
|
|
121
|
-
const processor: IMainCommandProcessor<IMyOptions> = async (options) => {
|
|
122
|
-
console.log('Processing with options:', options)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Create a command creator
|
|
126
|
-
const creator: IMainCommandCreator<IMyOptions> = (handle) => {
|
|
127
|
-
return new Command()
|
|
128
|
-
.name('build')
|
|
129
|
-
.description('Build the project')
|
|
130
|
-
.action(handle ? (opts) => handle(opts) : () => {})
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Create and use a mounter for adding to parent command
|
|
134
|
-
const mounter = createMainCommandMounter(creator, processor)
|
|
135
|
-
mounter(program)
|
|
136
|
-
|
|
137
|
-
// Or create an executor for direct execution
|
|
138
|
-
const executor = createMainCommandExecutor(creator, processor)
|
|
139
|
-
await executor(process.argv)
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
### Sub-Command Architecture
|
|
76
|
+
const cli = new Command({
|
|
77
|
+
name: 'mycli',
|
|
78
|
+
version: '1.0.0',
|
|
79
|
+
description: 'My awesome CLI tool',
|
|
80
|
+
})
|
|
143
81
|
|
|
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
|
-
public async resolveProcessor(args: string[], options: IBuildOptions): Promise<ISubCommandProcessor<IBuildOptions>> {
|
|
169
|
-
return {
|
|
170
|
-
process: async (args, options) => {
|
|
171
|
-
console.log(`Building for ${options.target}`)
|
|
172
|
-
if (options.production) {
|
|
173
|
-
console.log('Production mode enabled')
|
|
174
|
-
}
|
|
175
|
-
}
|
|
82
|
+
cli
|
|
83
|
+
.option({
|
|
84
|
+
long: 'verbose',
|
|
85
|
+
short: 'v',
|
|
86
|
+
type: 'boolean',
|
|
87
|
+
description: 'Enable verbose output',
|
|
88
|
+
})
|
|
89
|
+
.option({
|
|
90
|
+
long: 'output',
|
|
91
|
+
short: 'o',
|
|
92
|
+
type: 'string',
|
|
93
|
+
description: 'Output file path',
|
|
94
|
+
default: './output.txt',
|
|
95
|
+
})
|
|
96
|
+
.argument({
|
|
97
|
+
name: 'file',
|
|
98
|
+
kind: 'required',
|
|
99
|
+
description: 'Input file to process',
|
|
100
|
+
})
|
|
101
|
+
.action(({ opts, args, ctx }) => {
|
|
102
|
+
const [file] = args
|
|
103
|
+
ctx.reporter.info(`Processing ${file}...`)
|
|
104
|
+
if (opts['verbose']) {
|
|
105
|
+
ctx.reporter.debug(`Output: ${opts['output']}`)
|
|
176
106
|
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
107
|
+
})
|
|
179
108
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
109
|
+
cli.run({
|
|
110
|
+
argv: process.argv.slice(2),
|
|
111
|
+
envs: process.env,
|
|
112
|
+
})
|
|
183
113
|
```
|
|
184
114
|
|
|
185
|
-
###
|
|
115
|
+
### Subcommands
|
|
186
116
|
|
|
187
117
|
```typescript
|
|
188
|
-
import {
|
|
189
|
-
|
|
190
|
-
// Check if Git is available
|
|
191
|
-
if (hasGitInstalled()) {
|
|
192
|
-
console.log('Git is available')
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Install dependencies with user selection
|
|
196
|
-
await installDependencies({
|
|
197
|
-
cwd: process.cwd(),
|
|
198
|
-
plopBypass: [], // Or ['yarn'] to skip user prompt
|
|
199
|
-
reporter: myReporter
|
|
200
|
-
})
|
|
201
|
-
```
|
|
118
|
+
import { Command } from '@guanghechen/commander'
|
|
202
119
|
|
|
203
|
-
|
|
120
|
+
const root = new Command({
|
|
121
|
+
name: 'git',
|
|
122
|
+
version: '1.0.0',
|
|
123
|
+
description: 'A simple git-like CLI',
|
|
124
|
+
})
|
|
204
125
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
126
|
+
const clone = new Command({
|
|
127
|
+
name: 'clone',
|
|
128
|
+
description: 'Clone a repository',
|
|
129
|
+
})
|
|
130
|
+
.argument({ name: 'url', kind: 'required', description: 'Repository URL' })
|
|
131
|
+
.option({ long: 'depth', type: 'number', description: 'Shallow clone depth' })
|
|
132
|
+
.action(({ args, opts }) => {
|
|
133
|
+
console.log(`Cloning ${args[0]} with depth ${opts['depth'] ?? 'full'}`)
|
|
134
|
+
})
|
|
214
135
|
|
|
215
|
-
|
|
136
|
+
const commit = new Command({
|
|
137
|
+
name: 'commit',
|
|
138
|
+
aliases: ['ci'],
|
|
139
|
+
description: 'Record changes to the repository',
|
|
140
|
+
})
|
|
141
|
+
.option({ long: 'message', short: 'm', type: 'string', required: true, description: 'Commit message' })
|
|
142
|
+
.option({ long: 'amend', type: 'boolean', description: 'Amend previous commit' })
|
|
143
|
+
.action(({ opts }) => {
|
|
144
|
+
console.log(`Committing: ${opts['message']}`)
|
|
145
|
+
})
|
|
216
146
|
|
|
217
|
-
|
|
147
|
+
root.subcommand(clone).subcommand(commit)
|
|
218
148
|
|
|
219
|
-
|
|
220
|
-
class Command {
|
|
221
|
-
action<T>(fn: ICommandActionCallback<T>): this // Register action callback with enhanced type safety
|
|
222
|
-
opts<T>(): T // Get options with proper type inference
|
|
223
|
-
}
|
|
149
|
+
root.run({ argv: process.argv.slice(2), envs: process.env })
|
|
224
150
|
```
|
|
225
151
|
|
|
226
|
-
|
|
152
|
+
### Shell Completion
|
|
227
153
|
|
|
228
154
|
```typescript
|
|
229
|
-
|
|
230
|
-
// Abstract properties
|
|
231
|
-
abstract readonly subCommandName: string // The name of the sub-command
|
|
232
|
-
abstract readonly aliases: string[] // Command aliases
|
|
233
|
-
|
|
234
|
-
// Abstract methods
|
|
235
|
-
abstract command(processor: ISubCommandProcessor<O>): Command
|
|
236
|
-
abstract resolveProcessor(args: string[], options: O): Promise<ISubCommandProcessor<O>>
|
|
237
|
-
|
|
238
|
-
// Instance methods
|
|
239
|
-
mount(parentCommand: Command, opts?: { isDefault?: boolean }): void
|
|
240
|
-
execute(parentCommand: Command, rawArgs: string[]): Promise<void>
|
|
241
|
-
process(args: string[], options: O): Promise<void>
|
|
242
|
-
}
|
|
243
|
-
```
|
|
155
|
+
import { Command, CompletionCommand } from '@guanghechen/commander'
|
|
244
156
|
|
|
245
|
-
|
|
157
|
+
const root = new Command({
|
|
158
|
+
name: 'mycli',
|
|
159
|
+
version: '1.0.0',
|
|
160
|
+
description: 'My CLI with completion support',
|
|
161
|
+
})
|
|
246
162
|
|
|
247
|
-
|
|
163
|
+
// Add completion subcommand
|
|
164
|
+
root.subcommand(new CompletionCommand(root))
|
|
248
165
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
--workspace // Working directory
|
|
254
|
-
--log-level // Logging level
|
|
255
|
-
--log-encoding // Log file encoding
|
|
256
|
-
--log-filepath // Log file path
|
|
257
|
-
--log-name // Logger name
|
|
258
|
-
--log-mode // Log format mode
|
|
259
|
-
--log-flight // Logger flight options
|
|
166
|
+
// Generate completion scripts:
|
|
167
|
+
// mycli completion --bash > ~/.local/share/bash-completion/completions/mycli
|
|
168
|
+
// mycli completion --fish > ~/.config/fish/completions/mycli.fish
|
|
169
|
+
// mycli completion --pwsh >> $PROFILE
|
|
260
170
|
```
|
|
261
171
|
|
|
262
|
-
|
|
172
|
+
### Option Types
|
|
263
173
|
|
|
264
174
|
```typescript
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
}
|
|
270
|
-
|
|
175
|
+
import { Command } from '@guanghechen/commander'
|
|
176
|
+
|
|
177
|
+
new Command({ name: 'example', description: 'Option types demo' })
|
|
178
|
+
// Boolean (flags)
|
|
179
|
+
.option({ long: 'debug', type: 'boolean', description: 'Enable debug mode' })
|
|
180
|
+
|
|
181
|
+
// String with choices
|
|
182
|
+
.option({
|
|
183
|
+
long: 'format',
|
|
184
|
+
type: 'string',
|
|
185
|
+
choices: ['json', 'yaml', 'toml'],
|
|
186
|
+
default: 'json',
|
|
187
|
+
description: 'Output format'
|
|
188
|
+
})
|
|
271
189
|
|
|
272
|
-
|
|
190
|
+
// Number
|
|
191
|
+
.option({ long: 'port', type: 'number', default: 3000, description: 'Server port' })
|
|
273
192
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
type ICommandActionCallback<T> = (
|
|
277
|
-
args: string[],
|
|
278
|
-
options: T,
|
|
279
|
-
extra: string[],
|
|
280
|
-
self: Command,
|
|
281
|
-
) => void | Promise<void> | never
|
|
282
|
-
|
|
283
|
-
// Sub-command processor interface
|
|
284
|
-
interface ISubCommandProcessor<O> {
|
|
285
|
-
process(args: string[], options: O): Promise<void>
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// Base interface for sub-command options
|
|
289
|
-
interface ISubCommandOptions extends ICommandConfigurationOptions {
|
|
290
|
-
// Inherits all configuration options
|
|
291
|
-
}
|
|
292
|
-
```
|
|
193
|
+
// Array (can be specified multiple times)
|
|
194
|
+
.option({ long: 'include', type: 'string[]', description: 'Files to include' })
|
|
293
195
|
|
|
294
|
-
|
|
196
|
+
// Required option
|
|
197
|
+
.option({ long: 'config', type: 'string', required: true, description: 'Config file' })
|
|
295
198
|
|
|
296
|
-
|
|
199
|
+
// Custom coercion
|
|
200
|
+
.option({
|
|
201
|
+
long: 'date',
|
|
202
|
+
type: 'string',
|
|
203
|
+
coerce: (value) => new Date(value),
|
|
204
|
+
description: 'Date value',
|
|
205
|
+
})
|
|
206
|
+
```
|
|
297
207
|
|
|
208
|
+
## Reference
|
|
298
209
|
|
|
299
|
-
[homepage]
|
|
300
|
-
[commander.js]: https://github.com/tj/commander.js/
|
|
210
|
+
- [homepage][homepage]
|
|
301
211
|
|
|
212
|
+
[homepage]:
|
|
213
|
+
https://github.com/guanghechen/sora/tree/@guanghechen/commander@1.0.0/packages/commander#readme
|