@nan0web/ui-cli 1.1.1 → 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/README.md +153 -203
- package/bin/cli.js +11 -0
- package/bin/nan0cli.js +86 -0
- package/package.json +27 -13
- package/src/CLI.js +22 -30
- package/src/CLiMessage.js +2 -3
- package/src/Command.js +26 -24
- package/src/CommandError.js +3 -5
- package/src/CommandHelp.js +40 -36
- package/src/CommandMessage.js +56 -40
- package/src/CommandParser.js +27 -25
- package/src/InputAdapter.js +630 -90
- package/src/OutputAdapter.js +7 -8
- package/src/README.md.js +241 -312
- package/src/components/Alert.js +3 -6
- package/src/components/prompt/Autocomplete.js +12 -0
- package/src/components/prompt/Confirm.js +29 -0
- package/src/components/prompt/DateTime.js +26 -0
- package/src/components/prompt/Input.js +15 -0
- package/src/components/prompt/Mask.js +12 -0
- package/src/components/prompt/Multiselect.js +26 -0
- package/src/components/prompt/Next.js +8 -0
- package/src/components/prompt/Password.js +13 -0
- package/src/components/prompt/Pause.js +9 -0
- package/src/components/prompt/ProgressBar.js +16 -0
- package/src/components/prompt/Select.js +29 -0
- package/src/components/prompt/Slider.js +16 -0
- package/src/components/prompt/Spinner.js +29 -0
- package/src/components/prompt/Toggle.js +13 -0
- package/src/components/prompt/Tree.js +17 -0
- package/src/components/view/Alert.js +78 -0
- package/src/components/view/Badge.js +11 -0
- package/src/components/view/Nav.js +23 -0
- package/src/components/view/Table.js +12 -0
- package/src/components/view/Toast.js +9 -0
- package/src/core/Component.js +79 -0
- package/src/core/PropValidation.js +138 -0
- package/src/core/render.js +37 -0
- package/src/index.js +85 -40
- package/src/test/PlaygroundTest.js +37 -25
- package/src/test/index.js +2 -4
- package/src/ui/alert.js +58 -0
- package/src/ui/autocomplete.js +86 -0
- package/src/ui/badge.js +35 -0
- package/src/ui/confirm.js +49 -0
- package/src/ui/date-time.js +45 -0
- package/src/ui/form.js +120 -55
- package/src/ui/index.js +18 -4
- package/src/ui/input.js +79 -152
- package/src/ui/mask.js +132 -0
- package/src/ui/multiselect.js +59 -0
- package/src/ui/nav.js +74 -0
- package/src/ui/next.js +18 -13
- package/src/ui/progress.js +88 -0
- package/src/ui/select.js +49 -72
- package/src/ui/slider.js +154 -0
- package/src/ui/spinner.js +65 -0
- package/src/ui/table.js +163 -0
- package/src/ui/toast.js +34 -0
- package/src/ui/toggle.js +34 -0
- package/src/ui/tree.js +393 -0
- package/src/utils/parse.js +1 -1
- package/types/CLI.d.ts +5 -5
- package/types/CLiMessage.d.ts +1 -1
- package/types/Command.d.ts +2 -2
- package/types/CommandHelp.d.ts +3 -3
- package/types/CommandMessage.d.ts +8 -8
- package/types/CommandParser.d.ts +3 -3
- package/types/InputAdapter.d.ts +149 -15
- package/types/OutputAdapter.d.ts +1 -1
- package/types/README.md.d.ts +1 -1
- package/types/UiMessage.d.ts +31 -29
- package/types/components/prompt/Autocomplete.d.ts +6 -0
- package/types/components/prompt/Confirm.d.ts +6 -0
- package/types/components/prompt/DateTime.d.ts +6 -0
- package/types/components/prompt/Input.d.ts +6 -0
- package/types/components/prompt/Mask.d.ts +6 -0
- package/types/components/prompt/Multiselect.d.ts +6 -0
- package/types/components/prompt/Next.d.ts +6 -0
- package/types/components/prompt/Password.d.ts +6 -0
- package/types/components/prompt/Pause.d.ts +6 -0
- package/types/components/prompt/ProgressBar.d.ts +12 -0
- package/types/components/prompt/Select.d.ts +18 -0
- package/types/components/prompt/Slider.d.ts +6 -0
- package/types/components/prompt/Spinner.d.ts +21 -0
- package/types/components/prompt/Toggle.d.ts +6 -0
- package/types/components/prompt/Tree.d.ts +6 -0
- package/types/components/view/Alert.d.ts +21 -0
- package/types/components/view/Badge.d.ts +5 -0
- package/types/components/view/Nav.d.ts +15 -0
- package/types/components/view/Table.d.ts +10 -0
- package/types/components/view/Toast.d.ts +5 -0
- package/types/core/Component.d.ts +34 -0
- package/types/core/PropValidation.d.ts +48 -0
- package/types/core/render.d.ts +6 -0
- package/types/index.d.ts +47 -15
- package/types/test/PlaygroundTest.d.ts +12 -8
- package/types/test/index.d.ts +1 -1
- package/types/ui/alert.d.ts +14 -0
- package/types/ui/autocomplete.d.ts +20 -0
- package/types/ui/badge.d.ts +8 -0
- package/types/ui/confirm.d.ts +21 -0
- package/types/ui/date-time.d.ts +19 -0
- package/types/ui/form.d.ts +43 -12
- package/types/ui/index.d.ts +17 -2
- package/types/ui/input.d.ts +31 -74
- package/types/ui/mask.d.ts +29 -0
- package/types/ui/multiselect.d.ts +25 -0
- package/types/ui/nav.d.ts +27 -0
- package/types/ui/progress.d.ts +43 -0
- package/types/ui/select.d.ts +25 -64
- package/types/ui/slider.d.ts +23 -0
- package/types/ui/spinner.d.ts +28 -0
- package/types/ui/table.d.ts +28 -0
- package/types/ui/toast.d.ts +8 -0
- package/types/ui/toggle.d.ts +17 -0
- package/types/ui/tree.d.ts +48 -0
package/src/CLI.js
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* @module CLi
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { Message, OutputMessage } from
|
|
8
|
-
import Logger from
|
|
9
|
-
import CommandParser from
|
|
7
|
+
import { Message, OutputMessage } from '@nan0web/co'
|
|
8
|
+
import Logger from '@nan0web/log'
|
|
9
|
+
import CommandParser from './CommandParser.js'
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Main CLi class.
|
|
@@ -28,17 +28,12 @@ export default class CLi {
|
|
|
28
28
|
* @param {Array<Function>} [input.Messages] - Message classes for root commands.
|
|
29
29
|
*/
|
|
30
30
|
constructor(input = {}) {
|
|
31
|
-
const {
|
|
32
|
-
argv = process.argv.slice(2),
|
|
33
|
-
commands = {},
|
|
34
|
-
logger,
|
|
35
|
-
Messages = [],
|
|
36
|
-
} = input
|
|
31
|
+
const { argv = process.argv.slice(2), commands = {}, logger, Messages = [] } = input
|
|
37
32
|
this.argv = argv.map(String).filter(Boolean)
|
|
38
33
|
this.logger = logger ?? new Logger({ level: Logger.detectLevel(this.argv) })
|
|
39
34
|
this.Messages = Messages
|
|
40
35
|
this.#commands = new Map(Object.entries(commands))
|
|
41
|
-
this.#commands.set(
|
|
36
|
+
this.#commands.set('help', () => this.#help())
|
|
42
37
|
if (Messages.length > 0) this.#registerMessageCommands(Messages)
|
|
43
38
|
}
|
|
44
39
|
|
|
@@ -53,14 +48,16 @@ export default class CLi {
|
|
|
53
48
|
* @param {any} cmdClasses - Array of Message classes exposing a `run` generator.
|
|
54
49
|
*/
|
|
55
50
|
#registerMessageCommands(cmdClasses) {
|
|
56
|
-
cmdClasses.forEach(Class => {
|
|
51
|
+
cmdClasses.forEach((Class) => {
|
|
57
52
|
const cmd = Class.name.toLowerCase()
|
|
58
53
|
this.#commands.set(cmd, async function* (msg) {
|
|
59
54
|
const validated = new Class(msg.body)
|
|
60
55
|
/** @ts-ignore – only `content` needed for tests */
|
|
61
|
-
yield new OutputMessage({
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
yield new OutputMessage({
|
|
57
|
+
content: [`Executed ${cmd} with body: ${JSON.stringify(validated.body)}`],
|
|
58
|
+
})
|
|
59
|
+
if (typeof Class.run === 'function') yield* Class.run(validated)
|
|
60
|
+
if (typeof validated.run === 'function') yield* validated.run(msg)
|
|
64
61
|
})
|
|
65
62
|
})
|
|
66
63
|
}
|
|
@@ -71,7 +68,7 @@ export default class CLi {
|
|
|
71
68
|
* @param {Message} [msg] - Optional pre‑built message.
|
|
72
69
|
* @returns {AsyncGenerator<OutputMessage>}
|
|
73
70
|
*/
|
|
74
|
-
async *
|
|
71
|
+
async *run(msg) {
|
|
75
72
|
// const command = msg?.body?.command ?? this.#parseCommandName()
|
|
76
73
|
const command =
|
|
77
74
|
msg?.body?.command ??
|
|
@@ -84,18 +81,17 @@ export default class CLi {
|
|
|
84
81
|
|
|
85
82
|
if (!fn) {
|
|
86
83
|
yield new OutputMessage(`Unknown command: ${command}`)
|
|
87
|
-
yield new OutputMessage(`Available commands: ${Array.from(this.#commands.keys()).join(
|
|
84
|
+
yield new OutputMessage(`Available commands: ${Array.from(this.#commands.keys()).join(', ')}`)
|
|
88
85
|
return
|
|
89
86
|
}
|
|
90
87
|
|
|
91
88
|
// When there are no message‑based commands we forward the original message.
|
|
92
|
-
const fullMsg =
|
|
93
|
-
? new CommandParser(this.Messages).parse(this.argv)
|
|
94
|
-
: msg
|
|
89
|
+
const fullMsg =
|
|
90
|
+
this.Messages.length > 0 ? new CommandParser(this.Messages).parse(this.argv) : msg
|
|
95
91
|
|
|
96
92
|
// `help` command – return a single OutputMessage that contains the three‑part body
|
|
97
93
|
// expected by the test suite.
|
|
98
|
-
if (command ===
|
|
94
|
+
if (command === 'help') {
|
|
99
95
|
yield* fn(fullMsg)
|
|
100
96
|
return
|
|
101
97
|
}
|
|
@@ -110,7 +106,7 @@ export default class CLi {
|
|
|
110
106
|
* @returns {string}
|
|
111
107
|
*/
|
|
112
108
|
#parseCommandName() {
|
|
113
|
-
return this.argv.find(arg => !arg.startsWith(
|
|
109
|
+
return this.argv.find((arg) => !arg.startsWith('-')) || 'help'
|
|
114
110
|
}
|
|
115
111
|
|
|
116
112
|
/**
|
|
@@ -118,19 +114,15 @@ export default class CLi {
|
|
|
118
114
|
*
|
|
119
115
|
* @yields {OutputMessage}
|
|
120
116
|
*/
|
|
121
|
-
async
|
|
122
|
-
const lines = [
|
|
117
|
+
async *#help() {
|
|
118
|
+
const lines = ['Available commands:']
|
|
123
119
|
for (const [name] of this.#commands) lines.push(` ${name}`)
|
|
124
120
|
|
|
125
121
|
// The test expects a *single* message whose `body` is an array with three items:
|
|
126
122
|
// 1. placeholder error line (when no message‑based commands exist)
|
|
127
123
|
// 2. meta object describing the invoked command
|
|
128
124
|
// 3. the array of help lines
|
|
129
|
-
const body = [
|
|
130
|
-
["No commands defined for the CLi"],
|
|
131
|
-
{ command: "help", msg: undefined },
|
|
132
|
-
lines,
|
|
133
|
-
]
|
|
125
|
+
const body = [['No commands defined for the CLi'], { command: 'help', msg: undefined }, lines]
|
|
134
126
|
|
|
135
127
|
/** @ts-ignore – only `content` needed for tests */
|
|
136
128
|
yield new OutputMessage({ body, content: lines })
|
|
@@ -145,7 +137,7 @@ export default class CLi {
|
|
|
145
137
|
*/
|
|
146
138
|
static from(input) {
|
|
147
139
|
if (input instanceof CLi) return input
|
|
148
|
-
if (input && typeof input ===
|
|
149
|
-
throw new TypeError(
|
|
140
|
+
if (input && typeof input === 'object') return new CLi(input)
|
|
141
|
+
throw new TypeError('CLi.from expects an object or CLi instance')
|
|
150
142
|
}
|
|
151
143
|
}
|
package/src/CLiMessage.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { UiMessage } from
|
|
1
|
+
import { UiMessage } from '@nan0web/ui'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @class CLiMessage
|
|
5
5
|
* @deprecated Use UiMessage that is the same
|
|
6
6
|
* @extends UiMessage
|
|
7
7
|
*/
|
|
8
|
-
export default class CLiMessage extends UiMessage {
|
|
9
|
-
}
|
|
8
|
+
export default class CLiMessage extends UiMessage {}
|
package/src/Command.js
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* @module Command
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { Message } from
|
|
8
|
-
import CommandMessage from
|
|
9
|
-
import CommandError from
|
|
7
|
+
import { Message } from '@nan0web/co'
|
|
8
|
+
import CommandMessage from './CommandMessage.js'
|
|
9
|
+
import CommandError from './CommandError.js'
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Represents a command definition.
|
|
@@ -15,8 +15,8 @@ import CommandError from "./CommandError.js"
|
|
|
15
15
|
* @deprecated Use CLI instead
|
|
16
16
|
*/
|
|
17
17
|
export default class Command {
|
|
18
|
-
/** @type {string} */ name =
|
|
19
|
-
/** @type {string} */ help =
|
|
18
|
+
/** @type {string} */ name = ''
|
|
19
|
+
/** @type {string} */ help = ''
|
|
20
20
|
/** @type {Object} */ options = {}
|
|
21
21
|
/** @type {Function} */ run = async function* () {}
|
|
22
22
|
/** @type {Command[]} */ children = []
|
|
@@ -30,10 +30,10 @@ export default class Command {
|
|
|
30
30
|
* @param {Command[]} [config.children] - Sub‑commands.
|
|
31
31
|
*/
|
|
32
32
|
constructor(config) {
|
|
33
|
-
this.name = config.name ||
|
|
34
|
-
this.help = config.help ||
|
|
33
|
+
this.name = config.name || ''
|
|
34
|
+
this.help = config.help || ''
|
|
35
35
|
this.options = config.options || {}
|
|
36
|
-
this.run = config.run ||
|
|
36
|
+
this.run = config.run || async function* () {}
|
|
37
37
|
this.children = config.children || []
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -55,7 +55,7 @@ export default class Command {
|
|
|
55
55
|
* @returns {Command|null}
|
|
56
56
|
*/
|
|
57
57
|
findSubcommand(name) {
|
|
58
|
-
return this.children.find(c => c.name === name) || null
|
|
58
|
+
return this.children.find((c) => c.name === name) || null
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/**
|
|
@@ -66,14 +66,14 @@ export default class Command {
|
|
|
66
66
|
*/
|
|
67
67
|
parse(argv) {
|
|
68
68
|
const args = Array.isArray(argv) ? argv : [argv]
|
|
69
|
-
const msg = new CommandMessage({ name:
|
|
69
|
+
const msg = new CommandMessage({ name: '', argv: [], opts: {} })
|
|
70
70
|
let i = 0
|
|
71
71
|
while (i < args.length) {
|
|
72
72
|
const cur = args[i]
|
|
73
|
-
if (cur.startsWith(
|
|
73
|
+
if (cur.startsWith('--')) {
|
|
74
74
|
handleLongOption(msg, args, i)
|
|
75
75
|
i = updateIndexAfterOption(args, i)
|
|
76
|
-
} else if (cur.startsWith(
|
|
76
|
+
} else if (cur.startsWith('-')) {
|
|
77
77
|
handleShortOption(msg, args, i)
|
|
78
78
|
i = updateIndexAfterOption(args, i)
|
|
79
79
|
} else {
|
|
@@ -84,7 +84,7 @@ export default class Command {
|
|
|
84
84
|
}
|
|
85
85
|
if (msg.name === this.name) {
|
|
86
86
|
msg.argv = [...msg.argv]
|
|
87
|
-
msg.name =
|
|
87
|
+
msg.name = ''
|
|
88
88
|
}
|
|
89
89
|
if (msg.argv.length > 0) {
|
|
90
90
|
const subName = msg.argv[0]
|
|
@@ -108,9 +108,11 @@ export default class Command {
|
|
|
108
108
|
generateHelp() {
|
|
109
109
|
const parts = []
|
|
110
110
|
if (this.help) parts.push(this.help)
|
|
111
|
-
const optFlags = Object.keys(this.options)
|
|
111
|
+
const optFlags = Object.keys(this.options)
|
|
112
|
+
.map((k) => `--${k}`)
|
|
113
|
+
.join(' ')
|
|
112
114
|
parts.push(optFlags ? `Usage: ${this.name} ${optFlags}` : `Usage: ${this.name}`)
|
|
113
|
-
return parts.join(
|
|
115
|
+
return parts.join('\n')
|
|
114
116
|
}
|
|
115
117
|
|
|
116
118
|
/**
|
|
@@ -120,13 +122,13 @@ export default class Command {
|
|
|
120
122
|
* @yields {any}
|
|
121
123
|
* @throws {CommandError}
|
|
122
124
|
*/
|
|
123
|
-
async *
|
|
125
|
+
async *execute(message) {
|
|
124
126
|
try {
|
|
125
|
-
if (typeof this.run ===
|
|
127
|
+
if (typeof this.run === 'function') yield* this.run(message)
|
|
126
128
|
} catch (e) {
|
|
127
129
|
if (e instanceof CommandError) throw e
|
|
128
130
|
/** @ts-ignore */
|
|
129
|
-
throw new CommandError(
|
|
131
|
+
throw new CommandError('Command execution failed', { message: e.message, stack: e.stack })
|
|
130
132
|
}
|
|
131
133
|
}
|
|
132
134
|
|
|
@@ -139,7 +141,7 @@ export default class Command {
|
|
|
139
141
|
_applyDefaults(msg) {
|
|
140
142
|
for (const [opt, [type, def]] of Object.entries(this.options)) {
|
|
141
143
|
if (!(opt in msg.opts)) {
|
|
142
|
-
msg.opts[opt] = def !== undefined ? def : type === Boolean ? false :
|
|
144
|
+
msg.opts[opt] = def !== undefined ? def : type === Boolean ? false : ''
|
|
143
145
|
}
|
|
144
146
|
}
|
|
145
147
|
}
|
|
@@ -157,14 +159,14 @@ export default class Command {
|
|
|
157
159
|
*/
|
|
158
160
|
function handleLongOption(msg, argv, index) {
|
|
159
161
|
const cur = argv[index]
|
|
160
|
-
const eq = cur.indexOf(
|
|
162
|
+
const eq = cur.indexOf('=')
|
|
161
163
|
if (eq > -1) {
|
|
162
164
|
const k = cur.slice(2, eq)
|
|
163
165
|
const v = cur.slice(eq + 1)
|
|
164
166
|
msg.opts[k] = v
|
|
165
167
|
} else {
|
|
166
168
|
const k = cur.slice(2)
|
|
167
|
-
if (index + 1 < argv.length && !argv[index + 1].startsWith(
|
|
169
|
+
if (index + 1 < argv.length && !argv[index + 1].startsWith('-')) {
|
|
168
170
|
msg.opts[k] = argv[index + 1]
|
|
169
171
|
} else {
|
|
170
172
|
msg.opts[k] = true
|
|
@@ -186,7 +188,7 @@ function handleShortOption(msg, argv, index) {
|
|
|
186
188
|
for (const ch of cur) msg.opts[ch] = true
|
|
187
189
|
} else {
|
|
188
190
|
const k = cur
|
|
189
|
-
if (index + 1 < argv.length && !argv[index + 1].startsWith(
|
|
191
|
+
if (index + 1 < argv.length && !argv[index + 1].startsWith('-')) {
|
|
190
192
|
msg.opts[k] = argv[index + 1]
|
|
191
193
|
} else {
|
|
192
194
|
msg.opts[k] = true
|
|
@@ -204,7 +206,7 @@ function handleShortOption(msg, argv, index) {
|
|
|
204
206
|
*/
|
|
205
207
|
function updateIndexAfterOption(argv, index) {
|
|
206
208
|
const cur = argv[index]
|
|
207
|
-
if (cur.includes(
|
|
208
|
-
const hasVal = index + 1 < argv.length && !argv[index + 1].startsWith(
|
|
209
|
+
if (cur.includes('=')) return index + 1
|
|
210
|
+
const hasVal = index + 1 < argv.length && !argv[index + 1].startsWith('-')
|
|
209
211
|
return hasVal ? index + 2 : index + 1
|
|
210
212
|
}
|
package/src/CommandError.js
CHANGED
|
@@ -17,7 +17,7 @@ export default class CommandError extends Error {
|
|
|
17
17
|
*/
|
|
18
18
|
constructor(message, data = null) {
|
|
19
19
|
super(message)
|
|
20
|
-
this.name =
|
|
20
|
+
this.name = 'CommandError'
|
|
21
21
|
this.data = data
|
|
22
22
|
Error.captureStackTrace(this, CommandError)
|
|
23
23
|
}
|
|
@@ -28,8 +28,6 @@ export default class CommandError extends Error {
|
|
|
28
28
|
* @returns {string}
|
|
29
29
|
*/
|
|
30
30
|
toString() {
|
|
31
|
-
return this.data
|
|
32
|
-
? `${this.message}\n${JSON.stringify(this.data, null, 2)}`
|
|
33
|
-
: this.message
|
|
31
|
+
return this.data ? `${this.message}\n${JSON.stringify(this.data, null, 2)}` : this.message
|
|
34
32
|
}
|
|
35
|
-
}
|
|
33
|
+
}
|
package/src/CommandHelp.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Message } from
|
|
2
|
-
import Logger from
|
|
1
|
+
import { Message } from '@nan0web/co'
|
|
2
|
+
import Logger from '@nan0web/log'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @typedef {Object} CommandHelpField MessageBodySchema
|
|
@@ -55,7 +55,7 @@ export default class CommandHelp {
|
|
|
55
55
|
this.#usage(lines)
|
|
56
56
|
this.#options(lines)
|
|
57
57
|
this.#subcommands(lines)
|
|
58
|
-
return lines.join(
|
|
58
|
+
return lines.join('\n')
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/**
|
|
@@ -72,12 +72,13 @@ export default class CommandHelp {
|
|
|
72
72
|
*/
|
|
73
73
|
#header(lines) {
|
|
74
74
|
const name = this.MessageClass.name.toLowerCase()
|
|
75
|
-
const help = this.MessageClass['help'] ||
|
|
76
|
-
lines.push(
|
|
77
|
-
`${this.Logger.style(name, { color: this.Logger.MAGENTA })}`,
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
75
|
+
const help = this.MessageClass['help'] || ''
|
|
76
|
+
lines.push(
|
|
77
|
+
[`${this.Logger.style(name, { color: this.Logger.MAGENTA })}`, help]
|
|
78
|
+
.filter(Boolean)
|
|
79
|
+
.join(' • ')
|
|
80
|
+
)
|
|
81
|
+
lines.push('')
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
/**
|
|
@@ -92,17 +93,17 @@ export default class CommandHelp {
|
|
|
92
93
|
|
|
93
94
|
if (bodyProps.length === 0) {
|
|
94
95
|
lines.push(`Usage: ${name}`)
|
|
95
|
-
lines.push(
|
|
96
|
+
lines.push('')
|
|
96
97
|
return
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
const placeholderParts = []
|
|
100
101
|
const flagParts = []
|
|
101
102
|
|
|
102
|
-
bodyProps.forEach(prop => {
|
|
103
|
+
bodyProps.forEach((prop) => {
|
|
103
104
|
/** @type {CommandHelpField} */
|
|
104
105
|
const schema = this.BodyClass[prop] || {}
|
|
105
|
-
const alias = schema.alias ? `-${schema.alias}, ` :
|
|
106
|
+
const alias = schema.alias ? `-${schema.alias}, ` : ''
|
|
106
107
|
const placeholder = schema.placeholder || schema.defaultValue
|
|
107
108
|
if (placeholder) {
|
|
108
109
|
placeholderParts.push(`[${alias}--${prop}=${placeholder}]`)
|
|
@@ -116,22 +117,22 @@ export default class CommandHelp {
|
|
|
116
117
|
// * when placeholders exist:
|
|
117
118
|
// – if exactly ONE flag part → prepend with ", "
|
|
118
119
|
// – otherwise just space‑separate all parts.
|
|
119
|
-
let usage =
|
|
120
|
+
let usage = ''
|
|
120
121
|
if (placeholderParts.length) {
|
|
121
|
-
usage = placeholderParts.join(
|
|
122
|
+
usage = placeholderParts.join(' ')
|
|
122
123
|
if (flagParts.length) {
|
|
123
124
|
if (flagParts.length === 1) {
|
|
124
125
|
usage = `${usage}, ${flagParts[0]}`
|
|
125
126
|
} else {
|
|
126
|
-
usage = `${usage} ${flagParts.join(
|
|
127
|
+
usage = `${usage} ${flagParts.join(' ')}`
|
|
127
128
|
}
|
|
128
129
|
}
|
|
129
130
|
} else {
|
|
130
131
|
// only flag parts
|
|
131
|
-
usage = flagParts.join(
|
|
132
|
+
usage = flagParts.join(' , ')
|
|
132
133
|
}
|
|
133
134
|
lines.push(`Usage: ${name} ${usage}`)
|
|
134
|
-
lines.push(
|
|
135
|
+
lines.push('')
|
|
135
136
|
}
|
|
136
137
|
|
|
137
138
|
/**
|
|
@@ -144,27 +145,30 @@ export default class CommandHelp {
|
|
|
144
145
|
const bodyProps = Object.keys(bodyInstance)
|
|
145
146
|
if (bodyProps.length === 0) return
|
|
146
147
|
|
|
147
|
-
lines.push(
|
|
148
|
-
bodyProps.forEach(prop => {
|
|
148
|
+
lines.push('Options:')
|
|
149
|
+
bodyProps.forEach((prop) => {
|
|
149
150
|
/** @type {CommandHelpField} */
|
|
150
151
|
const schema = this.BodyClass[prop] || {}
|
|
151
|
-
if (typeof schema !==
|
|
152
|
-
|
|
153
|
-
const flags = schema.alias
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
152
|
+
if (typeof schema !== 'object') return
|
|
153
|
+
|
|
154
|
+
const flags = schema.alias ? `--${prop}, -${schema.alias}` : `--${prop}`
|
|
155
|
+
|
|
156
|
+
const type =
|
|
157
|
+
undefined !== schema.type
|
|
158
|
+
? String(schema.type)
|
|
159
|
+
: undefined !== schema.defaultValue
|
|
160
|
+
? typeof schema.defaultValue
|
|
161
|
+
: undefined !== schema.placeholder
|
|
162
|
+
? typeof schema.placeholder
|
|
163
|
+
: 'any'
|
|
164
|
+
const required =
|
|
165
|
+
schema.required || schema.pattern || schema.defaultValue === undefined ? ' *' : ' '
|
|
166
|
+
const description = schema.help || 'No description'
|
|
163
167
|
|
|
164
168
|
// Pad flags to align the type column with the expectations.
|
|
165
169
|
lines.push(` ${flags.padEnd(30)} ${type.padEnd(9)}${required} ${description}`)
|
|
166
170
|
})
|
|
167
|
-
lines.push(
|
|
171
|
+
lines.push('')
|
|
168
172
|
}
|
|
169
173
|
|
|
170
174
|
/**
|
|
@@ -185,12 +189,12 @@ export default class CommandHelp {
|
|
|
185
189
|
const children = this.MessageClass['Children'] || []
|
|
186
190
|
if (children.length === 0) return
|
|
187
191
|
|
|
188
|
-
lines.push(
|
|
189
|
-
children.forEach(ChildClass => {
|
|
192
|
+
lines.push('Subcommands:')
|
|
193
|
+
children.forEach((ChildClass) => {
|
|
190
194
|
const childName = ChildClass.name.toLowerCase()
|
|
191
|
-
const childHelp = ChildClass.help ||
|
|
195
|
+
const childHelp = ChildClass.help || 'No description'
|
|
192
196
|
lines.push(` ${childName.padEnd(20)} ${childHelp}`)
|
|
193
197
|
})
|
|
194
|
-
lines.push(
|
|
198
|
+
lines.push('')
|
|
195
199
|
}
|
|
196
200
|
}
|
package/src/CommandMessage.js
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* @module CommandMessage
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { Message } from
|
|
8
|
-
import { str2argv } from
|
|
9
|
-
import CommandError from
|
|
7
|
+
import { Message } from '@nan0web/co'
|
|
8
|
+
import { str2argv } from './utils/parse.js'
|
|
9
|
+
import CommandError from './CommandError.js'
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* @class
|
|
@@ -14,7 +14,7 @@ import CommandError from "./CommandError.js"
|
|
|
14
14
|
* @extends Message
|
|
15
15
|
*/
|
|
16
16
|
export default class CommandMessage extends Message {
|
|
17
|
-
#name =
|
|
17
|
+
#name = ''
|
|
18
18
|
#argv = []
|
|
19
19
|
#opts = {}
|
|
20
20
|
#children = []
|
|
@@ -30,14 +30,8 @@ export default class CommandMessage extends Message {
|
|
|
30
30
|
constructor(input = {}) {
|
|
31
31
|
super(input)
|
|
32
32
|
/** @type {any} */
|
|
33
|
-
const data = typeof input ===
|
|
34
|
-
const {
|
|
35
|
-
name = "",
|
|
36
|
-
argv = [],
|
|
37
|
-
opts = {},
|
|
38
|
-
children = [],
|
|
39
|
-
body = {},
|
|
40
|
-
} = data
|
|
33
|
+
const data = typeof input === 'object' && !Array.isArray(input) ? input : {}
|
|
34
|
+
const { name = '', argv = [], opts = {}, children = [], body = {} } = data
|
|
41
35
|
|
|
42
36
|
const fullBody = { ...body, ...opts }
|
|
43
37
|
this.body = fullBody
|
|
@@ -45,9 +39,9 @@ export default class CommandMessage extends Message {
|
|
|
45
39
|
this.#name = String(name)
|
|
46
40
|
this.#argv = argv.map(String)
|
|
47
41
|
this.#opts = opts
|
|
48
|
-
this.#children = children.map(c => CommandMessage.from(c))
|
|
42
|
+
this.#children = children.map((c) => CommandMessage.from(c))
|
|
49
43
|
|
|
50
|
-
if (typeof input ===
|
|
44
|
+
if (typeof input === 'string' || Array.isArray(input)) {
|
|
51
45
|
const parsed = CommandMessage.parse(input)
|
|
52
46
|
this.#name = parsed.name
|
|
53
47
|
this.#argv = parsed.argv
|
|
@@ -57,38 +51,60 @@ export default class CommandMessage extends Message {
|
|
|
57
51
|
}
|
|
58
52
|
|
|
59
53
|
/** @returns {string} */
|
|
60
|
-
get name() {
|
|
54
|
+
get name() {
|
|
55
|
+
return this.#name
|
|
56
|
+
}
|
|
61
57
|
/** @param {string} v */
|
|
62
|
-
set name(v) {
|
|
58
|
+
set name(v) {
|
|
59
|
+
this.#name = String(v)
|
|
60
|
+
}
|
|
63
61
|
|
|
64
62
|
/** @returns {string[]} */
|
|
65
|
-
get argv() {
|
|
63
|
+
get argv() {
|
|
64
|
+
return this.#argv
|
|
65
|
+
}
|
|
66
66
|
/** @param {string[]} v */
|
|
67
|
-
set argv(v) {
|
|
67
|
+
set argv(v) {
|
|
68
|
+
this.#argv = v.map(String)
|
|
69
|
+
}
|
|
68
70
|
|
|
69
71
|
/** @returns {Object} */
|
|
70
|
-
get opts() {
|
|
72
|
+
get opts() {
|
|
73
|
+
return this.#opts
|
|
74
|
+
}
|
|
71
75
|
/** @param {Object} v */
|
|
72
|
-
set opts(v) {
|
|
76
|
+
set opts(v) {
|
|
77
|
+
this.#opts = v
|
|
78
|
+
}
|
|
73
79
|
|
|
74
80
|
/** @returns {Array<CommandMessage>} */
|
|
75
|
-
get children() {
|
|
81
|
+
get children() {
|
|
82
|
+
return this.#children
|
|
83
|
+
}
|
|
76
84
|
|
|
77
85
|
/** @returns {Array<string>} Full command line (name + args). */
|
|
78
|
-
get args() {
|
|
86
|
+
get args() {
|
|
87
|
+
return [this.name, ...this.argv].filter(Boolean)
|
|
88
|
+
}
|
|
79
89
|
|
|
80
90
|
/** @returns {string} Sub‑command name of the first child, or empty string. */
|
|
81
|
-
get subCommand() {
|
|
91
|
+
get subCommand() {
|
|
92
|
+
return this.children[0]?.name || ''
|
|
93
|
+
}
|
|
82
94
|
|
|
83
95
|
/** @returns {CommandMessage|null} First child message, or null. */
|
|
84
|
-
get subCommandMessage() {
|
|
96
|
+
get subCommandMessage() {
|
|
97
|
+
return this.children[0] || null
|
|
98
|
+
}
|
|
85
99
|
|
|
86
100
|
/**
|
|
87
101
|
* Append a child {@link CommandMessage}.
|
|
88
102
|
*
|
|
89
103
|
* @param {CommandMessage|Object} msg
|
|
90
104
|
*/
|
|
91
|
-
add(msg) {
|
|
105
|
+
add(msg) {
|
|
106
|
+
this.#children.push(CommandMessage.from(msg))
|
|
107
|
+
}
|
|
92
108
|
|
|
93
109
|
/**
|
|
94
110
|
* Convert the message back to a command‑line string.
|
|
@@ -98,9 +114,9 @@ export default class CommandMessage extends Message {
|
|
|
98
114
|
toString() {
|
|
99
115
|
const optsStr = Object.entries(this.opts)
|
|
100
116
|
.map(([k, v]) => (v === true ? `--${k}` : `--${k} ${String(v)}`))
|
|
101
|
-
.join(
|
|
102
|
-
const argsStr = this.argv.join(
|
|
103
|
-
return [this.name, argsStr, optsStr].filter(Boolean).join(
|
|
117
|
+
.join(' ')
|
|
118
|
+
const argsStr = this.argv.join(' ')
|
|
119
|
+
return [this.name, argsStr, optsStr].filter(Boolean).join(' ')
|
|
104
120
|
}
|
|
105
121
|
|
|
106
122
|
/**
|
|
@@ -112,37 +128,37 @@ export default class CommandMessage extends Message {
|
|
|
112
128
|
* @throws {CommandError} If no input is supplied.
|
|
113
129
|
*/
|
|
114
130
|
static parse(argv, BodyClass) {
|
|
115
|
-
if (typeof argv ===
|
|
116
|
-
if (argv.length === 0) throw new CommandError(
|
|
117
|
-
const result = { name:
|
|
131
|
+
if (typeof argv === 'string') argv = str2argv(argv)
|
|
132
|
+
if (argv.length === 0) throw new CommandError('No input provided')
|
|
133
|
+
const result = { name: '', argv: [], opts: {} }
|
|
118
134
|
let i = 0
|
|
119
|
-
if (!argv[0].startsWith(
|
|
135
|
+
if (!argv[0].startsWith('-')) {
|
|
120
136
|
result.name = argv[0]
|
|
121
137
|
i = 1
|
|
122
138
|
}
|
|
123
139
|
while (i < argv.length) {
|
|
124
140
|
const cur = argv[i]
|
|
125
|
-
if (cur.startsWith(
|
|
126
|
-
const eq = cur.indexOf(
|
|
141
|
+
if (cur.startsWith('--')) {
|
|
142
|
+
const eq = cur.indexOf('=')
|
|
127
143
|
if (eq > -1) {
|
|
128
144
|
const k = cur.slice(2, eq)
|
|
129
145
|
const v = cur.slice(eq + 1)
|
|
130
146
|
result.opts[k] = v
|
|
131
147
|
} else {
|
|
132
148
|
const k = cur.slice(2)
|
|
133
|
-
if (i + 1 < argv.length && !argv[i + 1].startsWith(
|
|
149
|
+
if (i + 1 < argv.length && !argv[i + 1].startsWith('-')) {
|
|
134
150
|
result.opts[k] = argv[++i]
|
|
135
151
|
} else {
|
|
136
152
|
result.opts[k] = true
|
|
137
153
|
}
|
|
138
154
|
}
|
|
139
|
-
} else if (cur.startsWith(
|
|
155
|
+
} else if (cur.startsWith('-') && cur.length > 1) {
|
|
140
156
|
const shorts = cur.slice(1)
|
|
141
157
|
if (shorts.length > 1) {
|
|
142
|
-
shorts.split(
|
|
158
|
+
shorts.split('').forEach((s) => (result.opts[s] = true))
|
|
143
159
|
} else {
|
|
144
160
|
const k = shorts
|
|
145
|
-
if (i + 1 < argv.length && !argv[i + 1].startsWith(
|
|
161
|
+
if (i + 1 < argv.length && !argv[i + 1].startsWith('-')) {
|
|
146
162
|
result.opts[k] = argv[++i]
|
|
147
163
|
} else {
|
|
148
164
|
result.opts[k] = true
|
|
@@ -160,7 +176,7 @@ export default class CommandMessage extends Message {
|
|
|
160
176
|
msg.body = body
|
|
161
177
|
/** @ts-ignore */
|
|
162
178
|
const errors = body.getErrors?.() || {}
|
|
163
|
-
if (Object.keys(errors).length) throw new CommandError(
|
|
179
|
+
if (Object.keys(errors).length) throw new CommandError('Validation failed', { errors })
|
|
164
180
|
}
|
|
165
181
|
return msg
|
|
166
182
|
}
|
|
@@ -175,7 +191,7 @@ export default class CommandMessage extends Message {
|
|
|
175
191
|
if (input instanceof CommandMessage) return input
|
|
176
192
|
if (input instanceof Message) {
|
|
177
193
|
/** @ts-ignore */
|
|
178
|
-
return new CommandMessage({ body: input.body, name: input.name ||
|
|
194
|
+
return new CommandMessage({ body: input.body, name: input.name || '' })
|
|
179
195
|
}
|
|
180
196
|
return new CommandMessage(input)
|
|
181
197
|
}
|