@magic/cli 0.0.46 → 0.0.48
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 +17 -7
- package/package.json +17 -12
- package/src/{exec.mjs → exec.js} +7 -0
- package/src/{execFile.mjs → execFile.js} +8 -1
- package/src/help/{argToHelp.mjs → argToHelp.js} +11 -1
- package/src/help/{envToHelp.mjs → envToHelp.js} +13 -1
- package/src/help/findLongestString.js +19 -0
- package/src/help/index.js +100 -0
- package/src/index.js +75 -0
- package/src/parse/argv.js +125 -0
- package/src/parse/{clean.mjs → clean.js} +14 -1
- package/src/parse/{commands.mjs → commands.js} +14 -2
- package/src/parse/env.js +37 -0
- package/src/parse/{index.mjs → index.js} +12 -6
- package/src/parse/required.js +56 -0
- package/src/{prompt.mjs → prompt.js} +11 -2
- package/src/spawn.js +20 -0
- package/types/exec.d.ts +6 -0
- package/types/execFile.d.ts +5 -0
- package/types/help/argToHelp.d.ts +5 -0
- package/types/help/envToHelp.d.ts +1 -0
- package/types/help/findLongestString.d.ts +1 -0
- package/types/help/index.d.ts +67 -0
- package/types/index.d.ts +57 -0
- package/types/parse/argv.d.ts +35 -0
- package/types/parse/clean.d.ts +14 -0
- package/types/parse/commands.d.ts +11 -0
- package/types/parse/env.d.ts +15 -0
- package/types/parse/index.d.ts +1 -0
- package/types/parse/required.d.ts +16 -0
- package/types/prompt.d.ts +8 -0
- package/types/spawn.d.ts +5 -0
- package/src/help/findLongestString.mjs +0 -15
- package/src/help/index.mjs +0 -64
- package/src/index.mjs +0 -46
- package/src/parse/argv.mjs +0 -118
- package/src/parse/env.mjs +0 -22
- package/src/parse/required.mjs +0 -32
- package/src/spawn.mjs +0 -12
package/README.md
CHANGED
|
@@ -90,7 +90,7 @@ _those issues might get addressed in the future._
|
|
|
90
90
|
first, define the cli file
|
|
91
91
|
|
|
92
92
|
```javascript
|
|
93
|
-
// ./bin.
|
|
93
|
+
// ./bin.js
|
|
94
94
|
import { cli } from '@magic/cli'
|
|
95
95
|
|
|
96
96
|
const res = cli({
|
|
@@ -120,7 +120,7 @@ argv mappings handle options and option aliases
|
|
|
120
120
|
using the cli file above
|
|
121
121
|
|
|
122
122
|
```bash
|
|
123
|
-
./bin.
|
|
123
|
+
./bin.js -f1 arg1 arg2 -f2
|
|
124
124
|
```
|
|
125
125
|
|
|
126
126
|
resulting process.argv:
|
|
@@ -128,7 +128,7 @@ resulting process.argv:
|
|
|
128
128
|
```javascript
|
|
129
129
|
process.argv = [
|
|
130
130
|
'/path/to/bin/node',
|
|
131
|
-
'/path/to/bin.
|
|
131
|
+
'/path/to/bin.js',
|
|
132
132
|
'cmd1',
|
|
133
133
|
'--flag1'
|
|
134
134
|
'arg1',
|
|
@@ -169,7 +169,7 @@ cli commands will be handled too.
|
|
|
169
169
|
#### <a name="help-simple"></a>simple help message
|
|
170
170
|
|
|
171
171
|
```javascript
|
|
172
|
-
// ./bin.
|
|
172
|
+
// ./bin.js
|
|
173
173
|
|
|
174
174
|
import cli from '@magic/cli'
|
|
175
175
|
|
|
@@ -183,10 +183,10 @@ const args = {
|
|
|
183
183
|
const argv = cli(args)
|
|
184
184
|
```
|
|
185
185
|
|
|
186
|
-
then run ./bin.
|
|
186
|
+
then run ./bin.js without arguments
|
|
187
187
|
|
|
188
188
|
```bash
|
|
189
|
-
./bin.
|
|
189
|
+
./bin.js
|
|
190
190
|
|
|
191
191
|
// help output
|
|
192
192
|
`
|
|
@@ -552,6 +552,16 @@ update dependencies
|
|
|
552
552
|
- cli.exec allows stderrToStdout redirect config option
|
|
553
553
|
- update dependencies
|
|
554
554
|
|
|
555
|
-
##### 0.0.47
|
|
555
|
+
##### 0.0.47
|
|
556
|
+
|
|
557
|
+
- rename .mjs to js
|
|
558
|
+
- add more tests
|
|
559
|
+
- update dependencies
|
|
560
|
+
|
|
561
|
+
##### 0.0.48
|
|
562
|
+
|
|
563
|
+
- types changed to allow correct props when calling /src/index.js cli function
|
|
564
|
+
|
|
565
|
+
##### 0.0.49 - unreleased
|
|
556
566
|
|
|
557
567
|
- ...
|
package/package.json
CHANGED
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magic/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.48",
|
|
4
4
|
"homepage": "https://magic.github.io/cli",
|
|
5
5
|
"description": "declarative command line interfaces with aliasing, commands and environment sanitization",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"start": "t -p",
|
|
8
8
|
"format": "f -w --exclude docs",
|
|
9
9
|
"test": "t --exclude docs example config.js",
|
|
10
|
-
"build": "
|
|
10
|
+
"build": "tsc && npm run format",
|
|
11
|
+
"prepublishOnly": "npm run test && npm run build",
|
|
12
|
+
"build:docs": "NODE_ENV=production magic build",
|
|
11
13
|
"prod": "NODE_ENV=production magic build serve",
|
|
12
14
|
"dev": "magic dev",
|
|
13
15
|
"calls": "calls"
|
|
14
16
|
},
|
|
15
|
-
"main": "src/index.
|
|
17
|
+
"main": "src/index.js",
|
|
18
|
+
"types": "types/index.d.ts",
|
|
16
19
|
"type": "module",
|
|
17
20
|
"files": [
|
|
18
|
-
"src"
|
|
21
|
+
"src",
|
|
22
|
+
"types"
|
|
19
23
|
],
|
|
20
24
|
"engines": {
|
|
21
25
|
"node": ">=14.15.4"
|
|
@@ -32,11 +36,11 @@
|
|
|
32
36
|
"declarative"
|
|
33
37
|
],
|
|
34
38
|
"dependencies": {
|
|
35
|
-
"@magic/cases": "0.0.
|
|
36
|
-
"@magic/deep": "0.1.
|
|
37
|
-
"@magic/error": "0.0.
|
|
38
|
-
"@magic/log": "0.1.
|
|
39
|
-
"@magic/types": "0.1.
|
|
39
|
+
"@magic/cases": "0.0.10",
|
|
40
|
+
"@magic/deep": "0.1.18",
|
|
41
|
+
"@magic/error": "0.0.20",
|
|
42
|
+
"@magic/log": "0.1.20",
|
|
43
|
+
"@magic/types": "0.1.28"
|
|
40
44
|
},
|
|
41
45
|
"devDependencies": {
|
|
42
46
|
"@magic-modules/git-badges": "0.0.12",
|
|
@@ -44,9 +48,10 @@
|
|
|
44
48
|
"@magic-modules/no-spy": "0.0.9",
|
|
45
49
|
"@magic-modules/pre": "0.0.12",
|
|
46
50
|
"@magic-themes/docs": "0.0.15",
|
|
47
|
-
"@magic/core": "0.0.
|
|
48
|
-
"@magic/format": "0.0.
|
|
49
|
-
"@magic/test": "0.2.
|
|
51
|
+
"@magic/core": "0.0.156",
|
|
52
|
+
"@magic/format": "0.0.68",
|
|
53
|
+
"@magic/test": "0.2.20",
|
|
54
|
+
"typescript": "5.9.3"
|
|
50
55
|
},
|
|
51
56
|
"author": "Wizards & Witches",
|
|
52
57
|
"license": "AGPL-3.0",
|
package/src/{exec.mjs → exec.js}
RENAMED
|
@@ -4,6 +4,13 @@ import error from '@magic/error'
|
|
|
4
4
|
|
|
5
5
|
const libName = '@magic/cli.exec'
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Executes a shell command using child_process.exec
|
|
9
|
+
* @param {string} cmd - The shell command to execute.
|
|
10
|
+
* @param {object} [options={}] - Execution options.
|
|
11
|
+
* @param {boolean} [options.stderrToStdout=false] - If true, resolves stderr as stdout instead of rejecting.
|
|
12
|
+
* @returns {Promise<string>} Resolves with stdout or stderr (if stderrToStdout = true), rejects with Error.
|
|
13
|
+
*/
|
|
7
14
|
export const exec = (cmd, options = {}) =>
|
|
8
15
|
new Promise((resolve, reject) => {
|
|
9
16
|
const { stderrToStdout, ...opts } = options
|
|
@@ -4,6 +4,13 @@ import error from '@magic/error'
|
|
|
4
4
|
|
|
5
5
|
const libName = '@magic/cli.execFile'
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Executes a file using child_process.execFile
|
|
9
|
+
* @param {string} p - Path to the executable file.
|
|
10
|
+
* @param {string[]} [args=[]] - Arguments to pass to the executable.
|
|
11
|
+
* @param {import('child_process').ExecFileOptions} [opts={}] - Execution options.
|
|
12
|
+
* @returns {Promise<string | Buffer>} Resolves with stdout, rejects with Error.
|
|
13
|
+
*/
|
|
7
14
|
export const execFile = (p, args = [], opts = {}) =>
|
|
8
15
|
new Promise((resolve, reject) => {
|
|
9
16
|
child_process.execFile(
|
|
@@ -17,7 +24,7 @@ export const execFile = (p, args = [], opts = {}) =>
|
|
|
17
24
|
return
|
|
18
25
|
}
|
|
19
26
|
if (stderr) {
|
|
20
|
-
const e = error(new Error(`${libName}:
|
|
27
|
+
const e = error(new Error(`${libName}: error: ${stderr}`), 'E_EXECFILE_STDERR')
|
|
21
28
|
reject(e)
|
|
22
29
|
return
|
|
23
30
|
}
|
|
@@ -2,10 +2,19 @@ import is from '@magic/types'
|
|
|
2
2
|
import log from '@magic/log'
|
|
3
3
|
import error from '@magic/error'
|
|
4
4
|
|
|
5
|
-
import { findLongestString } from './findLongestString.
|
|
5
|
+
import { findLongestString } from './findLongestString.js'
|
|
6
6
|
|
|
7
7
|
const lib = '@magic/cli.help.argToHelp'
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Converts an array of CLI argument definitions into a formatted help string.
|
|
11
|
+
*
|
|
12
|
+
* @param {(string | string[])[]} arr - Array of argument names or alias arrays.
|
|
13
|
+
* @param {Record<string, string>} [help={}] - Optional help descriptions keyed by argument name.
|
|
14
|
+
* @param {Record<string, any>} [defaults={}] - Default values for each argument.
|
|
15
|
+
* @returns {string} A formatted help text block.
|
|
16
|
+
* @throws {Error} If `arr` is not a valid array.
|
|
17
|
+
*/
|
|
9
18
|
export const argToHelp = (arr, help = {}, defaults = {}) => {
|
|
10
19
|
if (!is.array(arr)) {
|
|
11
20
|
throw error(
|
|
@@ -19,6 +28,7 @@ export const argToHelp = (arr, help = {}, defaults = {}) => {
|
|
|
19
28
|
return arr
|
|
20
29
|
.map(opt => {
|
|
21
30
|
let name = opt
|
|
31
|
+
/** @type {string[]} */
|
|
22
32
|
let aliases = []
|
|
23
33
|
if (is.array(name)) {
|
|
24
34
|
const [n, ...al] = opt
|
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
import log from '@magic/log'
|
|
2
2
|
|
|
3
|
-
import { findLongestString } from './findLongestString.
|
|
3
|
+
import { findLongestString } from './findLongestString.js'
|
|
4
|
+
import is from '@magic/types'
|
|
4
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Converts an array of environment variable definitions into formatted help text.
|
|
8
|
+
*
|
|
9
|
+
* @param {Array<[string[], string, string]>} env - Array of tuples:
|
|
10
|
+
* [command aliases, variable name, value description].
|
|
11
|
+
* @returns {string} A formatted help text block for environment variables.
|
|
12
|
+
*/
|
|
5
13
|
export const envToHelp = env => {
|
|
14
|
+
if (!env || !is.array(env)) {
|
|
15
|
+
return ''
|
|
16
|
+
}
|
|
17
|
+
|
|
6
18
|
const envStrings = env.map(env => env[0])
|
|
7
19
|
|
|
8
20
|
const longest = findLongestString(envStrings)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import deep from '@magic/deep'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Finds the longest string within a (possibly nested) array.
|
|
5
|
+
*
|
|
6
|
+
* @param {Array<string | string[]>} arr - Array that may contain strings or nested arrays of strings.
|
|
7
|
+
* @returns {string} The longest string found in the array.
|
|
8
|
+
*/
|
|
9
|
+
export const findLongestString = arr => {
|
|
10
|
+
const sorted = arr.flat(200).sort((a, b) => {
|
|
11
|
+
if (a.length !== b.length) {
|
|
12
|
+
return b.length - a.length
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return a > b ? 1 : -1
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
return sorted[0]
|
|
19
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import log from '@magic/log'
|
|
2
|
+
import is from '@magic/types'
|
|
3
|
+
|
|
4
|
+
import { envToHelp } from './envToHelp.js'
|
|
5
|
+
import { argToHelp } from './argToHelp.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {object} ParsedCLI
|
|
9
|
+
* @property {Record<string, any>} [args] - Parsed CLI flags or arguments.
|
|
10
|
+
* @property {Record<string, any>} [commands] - Parsed subcommands.
|
|
11
|
+
* @property {Array<string|string[]>} [errors] - Validation errors or missing required args.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {object} CLIHelpMeta
|
|
16
|
+
* @property {string} [name] - CLI name shown in the help header.
|
|
17
|
+
* @property {string} [text] - General help text or description.
|
|
18
|
+
* @property {string|string[]} [example] - Example usage text or lines.
|
|
19
|
+
* @property {Record<string, string>} [commands] - Help text for commands.
|
|
20
|
+
* @property {Record<string, string>} [options] - Help text for options/flags.
|
|
21
|
+
* @property {Record<string, string>} [env] - Help text for environment variables.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {object} CLIArgs
|
|
26
|
+
* @property {ParsedCLI} [parsed] - Parsed CLI data (from argument parser).
|
|
27
|
+
* @property {Array<(string|string[])>} [commands] - CLI commands or argument definitions.
|
|
28
|
+
* @property {Array<(string|string[])>} [options] - CLI option/flag definitions.
|
|
29
|
+
* @property {Array<[string[], string, string]>} [env] - Environment variable mappings.
|
|
30
|
+
* @property {Record<string, any>} [default] - Default values for options or flags.
|
|
31
|
+
* @property {CLIHelpMeta} [help] - Help configuration or raw string help text.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Builds and returns formatted help text for a CLI command.
|
|
36
|
+
* Returns `false` if help is not requested and no errors occurred.
|
|
37
|
+
*
|
|
38
|
+
* @param {CLIArgs} args - The CLI arguments/config object.
|
|
39
|
+
* @returns {string|false} The generated help text or `false` if not shown.
|
|
40
|
+
*/
|
|
41
|
+
export const maybeHelp = args => {
|
|
42
|
+
const { parsed = {} } = args
|
|
43
|
+
|
|
44
|
+
const hasCommands = args.commands && Object.entries(args.commands).length > 0
|
|
45
|
+
const showCommandHelp =
|
|
46
|
+
hasCommands && parsed.commands && Object.keys(parsed.commands).length === 0
|
|
47
|
+
|
|
48
|
+
const helpRequested = parsed.args?.help
|
|
49
|
+
|
|
50
|
+
const showHelp = showCommandHelp || helpRequested || !is.empty(parsed.errors)
|
|
51
|
+
|
|
52
|
+
if (!showHelp) {
|
|
53
|
+
return false
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// add help text inspection here
|
|
57
|
+
const { commands = [], default: defaults = [], options = [], env = [], help = {} } = args
|
|
58
|
+
|
|
59
|
+
const commandHelp = argToHelp(commands, help.commands)
|
|
60
|
+
const optionHelp = argToHelp(options, help.options, defaults)
|
|
61
|
+
const envHelp = envToHelp(env)
|
|
62
|
+
|
|
63
|
+
const name = help.name || '@magic/cli wrapped cli.'
|
|
64
|
+
const header = is.string(help) ? help : help.text
|
|
65
|
+
|
|
66
|
+
const exampleArray = is.string(help.example) ? help.example.split('\n') : help.example || []
|
|
67
|
+
|
|
68
|
+
const exampleText = exampleArray
|
|
69
|
+
.map(a => {
|
|
70
|
+
if (a.trim().startsWith('#')) {
|
|
71
|
+
return log.color('green', a)
|
|
72
|
+
} else {
|
|
73
|
+
return a.trim()
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
.join('\n')
|
|
77
|
+
|
|
78
|
+
const helpArray = [
|
|
79
|
+
log.paint('green', name),
|
|
80
|
+
'\n',
|
|
81
|
+
header && `\n${log.paint('grey', header)}\n\n`,
|
|
82
|
+
commands.length && `${log.paint('grey', 'commands')}:\n${commandHelp}\n\n`,
|
|
83
|
+
options.length && `${log.paint('grey', 'flags')}:\n${optionHelp}\n\n`,
|
|
84
|
+
env.length && `${log.paint('grey', 'environment switches')}:\n${envHelp}\n\n`,
|
|
85
|
+
'examples:\n',
|
|
86
|
+
exampleText,
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
const errors = parsed.errors
|
|
90
|
+
?.map(e => `${log.paint('red', 'Error:')} ${is.arr(e) ? e.join(' or ') : e} is required`)
|
|
91
|
+
.filter(a => a)
|
|
92
|
+
.join('\n')
|
|
93
|
+
|
|
94
|
+
const errorMsg = `\n${errors}`
|
|
95
|
+
if (errors?.length) {
|
|
96
|
+
helpArray.push(errorMsg)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return helpArray.filter(a => a).join('')
|
|
100
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import log from '@magic/log'
|
|
2
|
+
|
|
3
|
+
import { maybeHelp } from './help/index.js'
|
|
4
|
+
import { parse } from './parse/index.js'
|
|
5
|
+
|
|
6
|
+
import { exec as execute } from './exec.js'
|
|
7
|
+
import { execFile as executeFile } from './execFile.js'
|
|
8
|
+
import { spawn as spawner } from './spawn.js'
|
|
9
|
+
import { prompt as promptUser } from './prompt.js'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {object} ParseProps
|
|
13
|
+
* @property {Array<string|string[]>} [options]
|
|
14
|
+
* @property {Record<string, any>|Array<any>} [prepend]
|
|
15
|
+
* @property {Record<string, any>|Array<any>} [append]
|
|
16
|
+
* @property {Record<string, any>} [default]
|
|
17
|
+
* @property {boolean} [pure]
|
|
18
|
+
* @property {boolean} [pureEnv]
|
|
19
|
+
* @property {boolean} [pureArgv]
|
|
20
|
+
* @property {boolean} [pureCommands]
|
|
21
|
+
* @property {Array<string|string[]>} [commands]
|
|
22
|
+
* @property {Array<[string[], string, string]>} [env]
|
|
23
|
+
* @property {Array<string|string[]>} [required]
|
|
24
|
+
* @property {object} [help]
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {object} ParsedCLI
|
|
29
|
+
* @property {Record<string, string>} env
|
|
30
|
+
* @property {Record<string, any>} argv
|
|
31
|
+
* @property {Record<string, any>} args
|
|
32
|
+
* @property {Record<string, boolean>} commands
|
|
33
|
+
* @property {Array<string|string[]>} errors
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Main CLI entry point
|
|
38
|
+
* @param {ParseProps} args - CLI configuration object.
|
|
39
|
+
* @returns {ParsedCLI}
|
|
40
|
+
*/
|
|
41
|
+
export const cli = (args = {}) => {
|
|
42
|
+
const { options = [] } = args
|
|
43
|
+
|
|
44
|
+
const hasHelpOption = options.some(option => option.includes('--help'))
|
|
45
|
+
if (!hasHelpOption) {
|
|
46
|
+
options.push(['--help', '-h'])
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
args.options = options
|
|
50
|
+
|
|
51
|
+
const parsed = parse(args)
|
|
52
|
+
|
|
53
|
+
const helpText = maybeHelp({ ...args, parsed })
|
|
54
|
+
|
|
55
|
+
if (helpText) {
|
|
56
|
+
log(helpText)
|
|
57
|
+
process.exit()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return parsed
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const spawn = spawner
|
|
64
|
+
cli.spawn = spawner
|
|
65
|
+
|
|
66
|
+
export const exec = execute
|
|
67
|
+
cli.exec = execute
|
|
68
|
+
|
|
69
|
+
export const prompt = promptUser
|
|
70
|
+
cli.prompt = promptUser
|
|
71
|
+
|
|
72
|
+
export const execFile = executeFile
|
|
73
|
+
cli.execFile = executeFile
|
|
74
|
+
|
|
75
|
+
export default cli
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import is from '@magic/types'
|
|
2
|
+
import cases from '@magic/cases'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {string | string[]} Option
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {Record<string, string | string[]>} ArgMap
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {object} ParseArgvProps
|
|
14
|
+
* @property {Option[]} [options] - CLI option names or aliases.
|
|
15
|
+
* @property {ArgMap | string[]} [prepend] - Arguments to prepend to process.argv.
|
|
16
|
+
* @property {ArgMap | string[]} [append] - Arguments to append to process.argv.
|
|
17
|
+
* @property {ArgMap} [default] - Default values for options.
|
|
18
|
+
* @property {boolean} [pure=false] - If true, do not modify process.argv.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {object} ParseArgvResult
|
|
23
|
+
* @property {Record<string, string[]>} argv - Raw argument key/value map.
|
|
24
|
+
* @property {Record<string, string[]>} args - Camel-cased argument key/value map.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Parses process.argv according to provided options, defaults, prepend/append arguments.
|
|
29
|
+
*
|
|
30
|
+
* @param {ParseArgvProps} props
|
|
31
|
+
* @returns {ParseArgvResult}
|
|
32
|
+
*/
|
|
33
|
+
export const parseArgv = (props = {}) => {
|
|
34
|
+
const { options = [], prepend = {}, append = {}, default: def = {}, pure = false } = props || {}
|
|
35
|
+
|
|
36
|
+
/** @type {string|undefined} */
|
|
37
|
+
let lastArg
|
|
38
|
+
|
|
39
|
+
/** @type {Record<string, string[]>} */
|
|
40
|
+
const argv = {}
|
|
41
|
+
|
|
42
|
+
const argvCopy = process.argv.slice()
|
|
43
|
+
|
|
44
|
+
// Handle prepend args first
|
|
45
|
+
if (prepend) {
|
|
46
|
+
if (is.array(prepend)) {
|
|
47
|
+
argvCopy.splice(2, 0, ...prepend.map(String))
|
|
48
|
+
} else {
|
|
49
|
+
Object.entries(prepend).forEach(([k, v]) => {
|
|
50
|
+
argv[k] = is.array(v) ? v.map(String) : [String(v)]
|
|
51
|
+
argvCopy.splice(2, 0, k, ...(is.array(v) ? v.map(String) : [String(v)]))
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Append arguments will be added after existing args
|
|
57
|
+
if (append) {
|
|
58
|
+
if (is.array(append)) {
|
|
59
|
+
argvCopy.push(...append.map(String))
|
|
60
|
+
} else {
|
|
61
|
+
Object.entries(append).forEach(([k, v]) => {
|
|
62
|
+
argv[k] = is.array(v) ? v.map(String) : [String(v)]
|
|
63
|
+
argvCopy.push(k, ...(is.array(v) ? v.map(String) : [String(v)]))
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Parsing loop
|
|
69
|
+
argvCopy.forEach((arg, i) => {
|
|
70
|
+
if (i <= 1) return
|
|
71
|
+
|
|
72
|
+
if (arg.startsWith('-')) {
|
|
73
|
+
/** @type {string} */
|
|
74
|
+
let argvArg = ''
|
|
75
|
+
options.forEach(option => {
|
|
76
|
+
if (is.array(option)) {
|
|
77
|
+
if (option.some(opt => opt === arg)) {
|
|
78
|
+
argvArg = option[0]
|
|
79
|
+
}
|
|
80
|
+
} else if (option === arg) {
|
|
81
|
+
argvArg = option
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
if (argvArg) {
|
|
86
|
+
lastArg = argvArg
|
|
87
|
+
argv[lastArg] = argv[lastArg] || []
|
|
88
|
+
if (!pure) argvCopy[i] = lastArg
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
if (lastArg) argv[lastArg].push(arg)
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
const [argv1, argv2, ...argvRest] = argvCopy
|
|
96
|
+
|
|
97
|
+
const entries = Object.entries(def)
|
|
98
|
+
|
|
99
|
+
if (entries.length) {
|
|
100
|
+
entries.forEach(([k, v]) => {
|
|
101
|
+
if (!argv[k] || argv[k].length === 0) {
|
|
102
|
+
argv[k] = is.array(v) ? v.map(String) : [String(v)]
|
|
103
|
+
argvRest.push(k)
|
|
104
|
+
if (!is.array(v)) v = [v]
|
|
105
|
+
v.forEach(vv => argvRest.push(String(vv)))
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!pure) {
|
|
111
|
+
process.argv = [argv1, argv2, ...argvRest]
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** @type {Record<string, string[]>} */
|
|
115
|
+
const args = {}
|
|
116
|
+
|
|
117
|
+
Object.entries(argv).forEach(([k, v]) => {
|
|
118
|
+
args[cases.camel(k)] = v
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
argv,
|
|
123
|
+
args,
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
import is from '@magic/types'
|
|
2
2
|
import cases from '@magic/cases'
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {object} CleanProps
|
|
6
|
+
* @property {string[]} [single] - List of single-value argument keys to clean.
|
|
7
|
+
* @property {Record<string, any>} [default] - Default values for arguments.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Cleans CLI parsed arguments for single-value keys, applying defaults.
|
|
12
|
+
*
|
|
13
|
+
* @param {import('../index.js').ParsedCLI} cli
|
|
14
|
+
* @param {CleanProps} [props={}]
|
|
15
|
+
* @returns {import('../index.js').ParsedCLI}
|
|
16
|
+
*/
|
|
4
17
|
export const clean = (cli, props = {}) => {
|
|
5
18
|
if (is.empty(props.single)) {
|
|
6
19
|
return cli
|
|
7
20
|
}
|
|
8
21
|
|
|
9
|
-
props.single
|
|
22
|
+
props.single?.map(s => {
|
|
10
23
|
const c = cases.camel(s)
|
|
11
24
|
const def = props.default && (props.default[s] || props.default[c])
|
|
12
25
|
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import log from '@magic/log'
|
|
2
2
|
import is from '@magic/types'
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {object} ParseCommandsProps
|
|
6
|
+
* @property {Array<string|string[]>} [commands] - Commands or alias arrays to check.
|
|
7
|
+
* @property {boolean} [pure=false] - If true, do not modify process.argv.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Parses CLI commands from process.argv.
|
|
12
|
+
*
|
|
13
|
+
* @param {ParseCommandsProps} props
|
|
14
|
+
* @returns {Record<string, boolean>} - Commands found in argv keyed by name.
|
|
15
|
+
*/
|
|
4
16
|
export const parseCommands = (props = {}) => {
|
|
5
17
|
const { commands = [], pure = false } = props
|
|
6
18
|
const { argv } = process
|
|
@@ -38,7 +50,7 @@ export const parseCommands = (props = {}) => {
|
|
|
38
50
|
}
|
|
39
51
|
|
|
40
52
|
if (idx > -1) {
|
|
41
|
-
if (!pure) {
|
|
53
|
+
if (!pure && !is.undefined(key)) {
|
|
42
54
|
argv[idx] = key
|
|
43
55
|
}
|
|
44
56
|
|
|
@@ -47,7 +59,7 @@ export const parseCommands = (props = {}) => {
|
|
|
47
59
|
return [key, true]
|
|
48
60
|
}
|
|
49
61
|
})
|
|
50
|
-
.filter(a => a),
|
|
62
|
+
.filter(a => !is.undefined(a)),
|
|
51
63
|
)
|
|
52
64
|
|
|
53
65
|
return cmds
|
package/src/parse/env.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import is from '@magic/types'
|
|
2
|
+
/**
|
|
3
|
+
* @typedef {[string|string[], string, string]} EnvTuple
|
|
4
|
+
* Tuple of: [CLI switches], environment variable name, value].
|
|
5
|
+
*
|
|
6
|
+
* @typedef {object} ParseEnvProps
|
|
7
|
+
* @property {EnvTuple[]} env - Environment variables to parse.
|
|
8
|
+
* @property {boolean} [pure=false] - If true, do not modify process.env.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Parses environment switches from process.argv.
|
|
13
|
+
*
|
|
14
|
+
* @param {ParseEnvProps} props
|
|
15
|
+
* @returns {Record<string, string>} - Environment variables set.
|
|
16
|
+
*/
|
|
17
|
+
export const parseEnv = ({ env = [], pure = false }) => {
|
|
18
|
+
/** @type {Record<string, string>} */
|
|
19
|
+
const environment = {}
|
|
20
|
+
|
|
21
|
+
// set env depending on env switches
|
|
22
|
+
env
|
|
23
|
+
.filter(([argv]) => {
|
|
24
|
+
if (!is.array(argv)) {
|
|
25
|
+
argv = [argv]
|
|
26
|
+
}
|
|
27
|
+
return argv.some(a => process.argv.includes(a))
|
|
28
|
+
})
|
|
29
|
+
.map(([_, key, val]) => {
|
|
30
|
+
environment[key] = val
|
|
31
|
+
if (!pure) {
|
|
32
|
+
process.env[key] = val
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
return environment
|
|
37
|
+
}
|
|
@@ -1,15 +1,21 @@
|
|
|
1
|
-
import { parseEnv } from './env.
|
|
2
|
-
import { parseArgv } from './argv.
|
|
3
|
-
import { parseCommands } from './commands.
|
|
4
|
-
import { parseRequired } from './required.
|
|
5
|
-
import { clean } from './clean.
|
|
1
|
+
import { parseEnv } from './env.js'
|
|
2
|
+
import { parseArgv } from './argv.js'
|
|
3
|
+
import { parseCommands } from './commands.js'
|
|
4
|
+
import { parseRequired } from './required.js'
|
|
5
|
+
import { clean } from './clean.js'
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Parses CLI arguments, commands, environment variables, and required options.
|
|
9
|
+
*
|
|
10
|
+
* @param {import('../index.js').ParseProps} props
|
|
11
|
+
* @returns {import('../index.js').ParsedCLI}
|
|
12
|
+
*/
|
|
7
13
|
export const parse = props => {
|
|
8
14
|
const { pure = false } = props
|
|
9
15
|
|
|
10
16
|
const { pureEnv = pure, pureArgv = pure, pureCommands = pure } = props
|
|
11
17
|
|
|
12
|
-
const env = parseEnv({
|
|
18
|
+
const env = parseEnv({ env: props.env || [], pure: pureEnv })
|
|
13
19
|
const { argv, args } = parseArgv({ ...props, pure: pureArgv })
|
|
14
20
|
const commands = parseCommands({ ...props, pure: pureCommands })
|
|
15
21
|
|