@magic/cli 0.0.46 → 0.0.47

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.
Files changed (39) hide show
  1. package/README.md +13 -7
  2. package/package.json +17 -12
  3. package/src/{exec.mjs → exec.js} +7 -0
  4. package/src/{execFile.mjs → execFile.js} +8 -1
  5. package/src/help/{argToHelp.mjs → argToHelp.js} +11 -1
  6. package/src/help/{envToHelp.mjs → envToHelp.js} +13 -1
  7. package/src/help/findLongestString.js +19 -0
  8. package/src/help/index.js +100 -0
  9. package/src/{index.mjs → index.js} +12 -7
  10. package/src/parse/argv.js +125 -0
  11. package/src/parse/{clean.mjs → clean.js} +14 -1
  12. package/src/parse/{commands.mjs → commands.js} +14 -2
  13. package/src/parse/env.js +37 -0
  14. package/src/parse/index.js +60 -0
  15. package/src/parse/required.js +56 -0
  16. package/src/{prompt.mjs → prompt.js} +11 -2
  17. package/src/spawn.js +20 -0
  18. package/types/exec.d.ts +6 -0
  19. package/types/execFile.d.ts +5 -0
  20. package/types/help/argToHelp.d.ts +5 -0
  21. package/types/help/envToHelp.d.ts +1 -0
  22. package/types/help/findLongestString.d.ts +1 -0
  23. package/types/help/index.d.ts +67 -0
  24. package/types/index.d.ts +36 -0
  25. package/types/parse/argv.d.ts +35 -0
  26. package/types/parse/clean.d.ts +14 -0
  27. package/types/parse/commands.d.ts +11 -0
  28. package/types/parse/env.d.ts +15 -0
  29. package/types/parse/index.d.ts +22 -0
  30. package/types/parse/required.d.ts +16 -0
  31. package/types/prompt.d.ts +8 -0
  32. package/types/spawn.d.ts +5 -0
  33. package/src/help/findLongestString.mjs +0 -15
  34. package/src/help/index.mjs +0 -64
  35. package/src/parse/argv.mjs +0 -118
  36. package/src/parse/env.mjs +0 -22
  37. package/src/parse/index.mjs +0 -29
  38. package/src/parse/required.mjs +0 -32
  39. package/src/spawn.mjs +0 -12
@@ -0,0 +1,56 @@
1
+ import is from '@magic/types'
2
+
3
+ /**
4
+ * @typedef {object} ParsedCLI
5
+ * @property {Record<string, string>} env
6
+ * @property {Record<string, any>} argv
7
+ * @property {Record<string, any>} args
8
+ * @property {Record<string, boolean>} commands
9
+ * @property {Array<string|string[]>} errors
10
+ */
11
+
12
+ /**
13
+ * @typedef {object} ParseRequiredArgs
14
+ * @property {ParsedCLI} [parsed]
15
+ * @property {object} [props]
16
+ * @property {Array<string|string[]> | string} [props.required]
17
+ */
18
+
19
+ /**
20
+ * Validates that required CLI arguments exist.
21
+ *
22
+ * @param {ParseRequiredArgs} args
23
+ * @returns {Array<string|string[]>} - List of missing required arguments.
24
+ */
25
+ export const parseRequired = (args = {}) => {
26
+ const { parsed, props } = args
27
+
28
+ if (is.empty(props?.required)) {
29
+ return []
30
+ }
31
+
32
+ /** @type {string[]} */
33
+ const errors = []
34
+
35
+ if (is.string(props?.required)) {
36
+ props.required = [props.required]
37
+ }
38
+
39
+ props?.required?.forEach(req => {
40
+ if (is.array(req)) {
41
+ const some = req.some(a => parsed?.argv[a])
42
+
43
+ if (!some) {
44
+ errors.push(...req)
45
+ }
46
+ } else {
47
+ const opt = parsed?.argv[req]
48
+
49
+ if (is.empty(opt)) {
50
+ errors.push(req)
51
+ }
52
+ }
53
+ })
54
+
55
+ return errors
56
+ }
@@ -1,6 +1,14 @@
1
1
  import is from '@magic/types'
2
2
  import readline from 'readline'
3
-
3
+ /**
4
+ * Prompts the user for input or confirmation.
5
+ * @param {string|string[]} [msg=''] - Message to display in the prompt.
6
+ * @param {object} [options={}] - Prompt options.
7
+ * @param {boolean} [options.yesNo=false] - Whether to treat the input as a yes/no confirmation.
8
+ * @param {boolean} [options.yesDefault=false] - If true, defaults to "yes" on empty input.
9
+ * @param {NodeJS.Process} [options.std=process] - Standard input/output streams.
10
+ * @returns {Promise<string|boolean>} Resolves with the user input (string) or boolean for yes/no prompts.
11
+ */
4
12
  export const prompt = (msg = '', options = {}) =>
5
13
  new Promise((resolve, reject) => {
6
14
  const { yesNo = false, std = process, yesDefault = false } = options
@@ -36,7 +44,8 @@ export const prompt = (msg = '', options = {}) =>
36
44
  rl.on('line', line => {
37
45
  if (yesNo) {
38
46
  const trimmed = line.trim().toLowerCase()
39
- line = trimmed === 'y' || trimmed === 'yes' || yesDefault
47
+ resolve(trimmed === 'y' || trimmed === 'yes' || yesDefault)
48
+ return
40
49
  }
41
50
 
42
51
  resolve(line)
package/src/spawn.js ADDED
@@ -0,0 +1,20 @@
1
+ import child_process from 'child_process'
2
+
3
+ /**
4
+ * Spawns a new process.
5
+ * @param {string} cmd - Command to run.
6
+ * @param {string[]} [args=[]] - Arguments for the command.
7
+ * @param {import('child_process').SpawnOptions} [opt={}] - Spawn options.
8
+ * @returns {import('child_process').ChildProcess} The spawned process.
9
+ */
10
+ export const spawn = (cmd, args = [], opt = {}) => {
11
+ /** @type {import('child_process').SpawnOptions} */
12
+ const opts = {
13
+ cwd: process.cwd(),
14
+ env: process.env,
15
+ stdio: 'inherit',
16
+ ...opt,
17
+ }
18
+
19
+ return child_process.spawn(cmd, args, opts)
20
+ }
@@ -0,0 +1,6 @@
1
+ export function exec(
2
+ cmd: string,
3
+ options?: {
4
+ stderrToStdout?: boolean | undefined
5
+ },
6
+ ): Promise<string>
@@ -0,0 +1,5 @@
1
+ export function execFile(
2
+ p: string,
3
+ args?: string[],
4
+ opts?: import('child_process').ExecFileOptions,
5
+ ): Promise<string | Buffer>
@@ -0,0 +1,5 @@
1
+ export function argToHelp(
2
+ arr: (string | string[])[],
3
+ help?: Record<string, string>,
4
+ defaults?: Record<string, any>,
5
+ ): string
@@ -0,0 +1 @@
1
+ export function envToHelp(env: Array<[string[], string, string]>): string
@@ -0,0 +1 @@
1
+ export function findLongestString(arr: Array<string | string[]>): string
@@ -0,0 +1,67 @@
1
+ export function maybeHelp(args: CLIArgs): string | false
2
+ export type ParsedCLI = {
3
+ /**
4
+ * - Parsed CLI flags or arguments.
5
+ */
6
+ args?: Record<string, any> | undefined
7
+ /**
8
+ * - Parsed subcommands.
9
+ */
10
+ commands?: Record<string, any> | undefined
11
+ /**
12
+ * - Validation errors or missing required args.
13
+ */
14
+ errors?: (string | string[])[] | undefined
15
+ }
16
+ export type CLIHelpMeta = {
17
+ /**
18
+ * - CLI name shown in the help header.
19
+ */
20
+ name?: string | undefined
21
+ /**
22
+ * - General help text or description.
23
+ */
24
+ text?: string | undefined
25
+ /**
26
+ * - Example usage text or lines.
27
+ */
28
+ example?: string | string[] | undefined
29
+ /**
30
+ * - Help text for commands.
31
+ */
32
+ commands?: Record<string, string> | undefined
33
+ /**
34
+ * - Help text for options/flags.
35
+ */
36
+ options?: Record<string, string> | undefined
37
+ /**
38
+ * - Help text for environment variables.
39
+ */
40
+ env?: Record<string, string> | undefined
41
+ }
42
+ export type CLIArgs = {
43
+ /**
44
+ * - Parsed CLI data (from argument parser).
45
+ */
46
+ parsed?: ParsedCLI | undefined
47
+ /**
48
+ * - CLI commands or argument definitions.
49
+ */
50
+ commands?: (string | string[])[] | undefined
51
+ /**
52
+ * - CLI option/flag definitions.
53
+ */
54
+ options?: (string | string[])[] | undefined
55
+ /**
56
+ * - Environment variable mappings.
57
+ */
58
+ env?: [string[], string, string][] | undefined
59
+ /**
60
+ * - Default values for options or flags.
61
+ */
62
+ default?: Record<string, any> | undefined
63
+ /**
64
+ * - Help configuration or raw string help text.
65
+ */
66
+ help?: CLIHelpMeta | undefined
67
+ }
@@ -0,0 +1,36 @@
1
+ export function cli(args?: { options?: (string | string[])[] | undefined }): object
2
+ export namespace cli {
3
+ export { spawner as spawn }
4
+ export { execute as exec }
5
+ export { promptUser as prompt }
6
+ export { executeFile as execFile }
7
+ }
8
+ export const spawn: (
9
+ cmd: string,
10
+ args?: string[],
11
+ opt?: import('child_process').SpawnOptions,
12
+ ) => import('child_process').ChildProcess
13
+ export const exec: (
14
+ cmd: string,
15
+ options?: {
16
+ stderrToStdout?: boolean | undefined
17
+ },
18
+ ) => Promise<string>
19
+ export const prompt: (
20
+ msg?: string | string[],
21
+ options?: {
22
+ yesNo?: boolean | undefined
23
+ yesDefault?: boolean | undefined
24
+ std?: NodeJS.Process | undefined
25
+ },
26
+ ) => Promise<string | boolean>
27
+ export const execFile: (
28
+ p: string,
29
+ args?: string[],
30
+ opts?: import('child_process').ExecFileOptions,
31
+ ) => Promise<string | Buffer>
32
+ export default cli
33
+ import { spawn as spawner } from './spawn.js'
34
+ import { exec as execute } from './exec.js'
35
+ import { prompt as promptUser } from './prompt.js'
36
+ import { execFile as executeFile } from './execFile.js'
@@ -0,0 +1,35 @@
1
+ export function parseArgv(props?: ParseArgvProps): ParseArgvResult
2
+ export type Option = string | string[]
3
+ export type ArgMap = Record<string, string | string[]>
4
+ export type ParseArgvProps = {
5
+ /**
6
+ * - CLI option names or aliases.
7
+ */
8
+ options?: Option[] | undefined
9
+ /**
10
+ * - Arguments to prepend to process.argv.
11
+ */
12
+ prepend?: string[] | ArgMap | undefined
13
+ /**
14
+ * - Arguments to append to process.argv.
15
+ */
16
+ append?: string[] | ArgMap | undefined
17
+ /**
18
+ * - Default values for options.
19
+ */
20
+ default?: ArgMap | undefined
21
+ /**
22
+ * - If true, do not modify process.argv.
23
+ */
24
+ pure?: boolean | undefined
25
+ }
26
+ export type ParseArgvResult = {
27
+ /**
28
+ * - Raw argument key/value map.
29
+ */
30
+ argv: Record<string, string[]>
31
+ /**
32
+ * - Camel-cased argument key/value map.
33
+ */
34
+ args: Record<string, string[]>
35
+ }
@@ -0,0 +1,14 @@
1
+ export function clean(
2
+ cli: import('./index.js').ParsedCLI,
3
+ props?: CleanProps,
4
+ ): import('./index.js').ParsedCLI
5
+ export type CleanProps = {
6
+ /**
7
+ * - List of single-value argument keys to clean.
8
+ */
9
+ single?: string[] | undefined
10
+ /**
11
+ * - Default values for arguments.
12
+ */
13
+ default?: Record<string, any> | undefined
14
+ }
@@ -0,0 +1,11 @@
1
+ export function parseCommands(props?: ParseCommandsProps): Record<string, boolean>
2
+ export type ParseCommandsProps = {
3
+ /**
4
+ * - Commands or alias arrays to check.
5
+ */
6
+ commands?: (string | string[])[] | undefined
7
+ /**
8
+ * - If true, do not modify process.argv.
9
+ */
10
+ pure?: boolean | undefined
11
+ }
@@ -0,0 +1,15 @@
1
+ export function parseEnv({ env, pure }: ParseEnvProps): Record<string, string>
2
+ /**
3
+ * Tuple of: [CLI switches], environment variable name, value].
4
+ */
5
+ export type EnvTuple = [string | string[], string, string]
6
+ export type ParseEnvProps = {
7
+ /**
8
+ * - Environment variables to parse.
9
+ */
10
+ env: EnvTuple[]
11
+ /**
12
+ * - If true, do not modify process.env.
13
+ */
14
+ pure?: boolean | undefined
15
+ }
@@ -0,0 +1,22 @@
1
+ export function parse(props: ParseProps): ParsedCLI
2
+ export type ParseProps = {
3
+ options?: (string | string[])[] | undefined
4
+ prepend?: any[] | Record<string, any> | undefined
5
+ append?: any[] | Record<string, any> | undefined
6
+ default?: Record<string, any> | undefined
7
+ pure?: boolean | undefined
8
+ pureEnv?: boolean | undefined
9
+ pureArgv?: boolean | undefined
10
+ pureCommands?: boolean | undefined
11
+ commands?: (string | string[])[] | undefined
12
+ env?: [string[], string, string][] | undefined
13
+ required?: (string | string[])[] | undefined
14
+ help?: object | undefined
15
+ }
16
+ export type ParsedCLI = {
17
+ env: Record<string, string>
18
+ argv: Record<string, any>
19
+ args: Record<string, any>
20
+ commands: Record<string, boolean>
21
+ errors: Array<string | string[]>
22
+ }
@@ -0,0 +1,16 @@
1
+ export function parseRequired(args?: ParseRequiredArgs): Array<string | string[]>
2
+ export type ParsedCLI = {
3
+ env: Record<string, string>
4
+ argv: Record<string, any>
5
+ args: Record<string, any>
6
+ commands: Record<string, boolean>
7
+ errors: Array<string | string[]>
8
+ }
9
+ export type ParseRequiredArgs = {
10
+ parsed?: ParsedCLI | undefined
11
+ props?:
12
+ | {
13
+ required?: string | (string | string[])[] | undefined
14
+ }
15
+ | undefined
16
+ }
@@ -0,0 +1,8 @@
1
+ export function prompt(
2
+ msg?: string | string[],
3
+ options?: {
4
+ yesNo?: boolean | undefined
5
+ yesDefault?: boolean | undefined
6
+ std?: NodeJS.Process | undefined
7
+ },
8
+ ): Promise<string | boolean>
@@ -0,0 +1,5 @@
1
+ export function spawn(
2
+ cmd: string,
3
+ args?: string[],
4
+ opt?: import('child_process').SpawnOptions,
5
+ ): import('child_process').ChildProcess
@@ -1,15 +0,0 @@
1
- import deep from '@magic/deep'
2
-
3
- export const findLongestString = arr => {
4
- arr = deep.flatten(arr)
5
-
6
- const sorted = arr.sort((a, b) => {
7
- if (a.length !== b.length) {
8
- return b.length - a.length
9
- }
10
-
11
- return a > b ? 1 : -1
12
- })
13
-
14
- return sorted[0]
15
- }
@@ -1,64 +0,0 @@
1
- import log from '@magic/log'
2
- import is from '@magic/types'
3
-
4
- import { envToHelp } from './envToHelp.mjs'
5
- import { argToHelp } from './argToHelp.mjs'
6
-
7
- export const maybeHelp = args => {
8
- const { parsed } = args
9
- const hasCommands = args.commands && Object.entries(args.commands).length > 0
10
- const showCommandHelp = hasCommands && Object.keys(parsed.commands).length === 0
11
-
12
- const helpRequested = parsed.args.help
13
-
14
- const showHelp = showCommandHelp || helpRequested || !is.empty(parsed.errors)
15
-
16
- if (!showHelp) {
17
- return false
18
- }
19
-
20
- // add help text inspection here
21
- const { commands = [], default: defaults = [], options = [], env = [], help = '' } = args
22
-
23
- const commandHelp = argToHelp(commands, help.commands)
24
- const optionHelp = argToHelp(options, help.options, defaults)
25
- const envHelp = envToHelp(env, help.env)
26
-
27
- const name = help.name || '@magic/cli wrapped cli.'
28
- const header = is.string(help) ? help : help.text
29
-
30
- const exampleArray = is.string(help.example) ? help.example.split('\n') : help.example
31
-
32
- const exampleText = exampleArray
33
- .map(a => {
34
- if (a.trim().startsWith('#')) {
35
- return log.color('green', a)
36
- } else {
37
- return a.trim()
38
- }
39
- })
40
- .join('\n')
41
-
42
- const helpArray = [
43
- log.paint('green', name),
44
- '\n',
45
- header && `\n${log.paint('grey', header)}\n\n`,
46
- commands.length && `${log.paint('grey', 'commands')}:\n${commandHelp}\n\n`,
47
- options.length && `${log.paint('grey', 'flags')}:\n${optionHelp}\n\n`,
48
- env.length && `${log.paint('grey', 'environment switches')}:\n${envHelp}\n\n`,
49
- 'examples:\n',
50
- exampleText,
51
- ]
52
-
53
- const errors = parsed.errors
54
- .map(e => `${log.paint('red', 'Error:')} ${is.arr(e) ? e.join(' or ') : e} is required`)
55
- .filter(a => a)
56
- .join('\n')
57
-
58
- const errorMsg = `\n${errors}`
59
- if (errors.length) {
60
- helpArray.push(errorMsg)
61
- }
62
-
63
- return helpArray.filter(a => a).join('')
64
- }
@@ -1,118 +0,0 @@
1
- import is from '@magic/types'
2
- import cases from '@magic/cases'
3
-
4
- export const parseArgv = ({
5
- options = [],
6
- prepend = {},
7
- append = {},
8
- default: def = {},
9
- pure = false,
10
- }) => {
11
- let lastArg
12
- const argv = {}
13
- // map over argv, find arguments and values.
14
- // arguments are all strings starting with a -,
15
- // values are all strings between strings starting with a -.
16
- process.argv.forEach((arg, i) => {
17
- if (i <= 1) {
18
- return arg
19
- }
20
-
21
- if (arg.startsWith('-')) {
22
- // this is a key
23
- let argvArg
24
- options.forEach(option => {
25
- if (is.array(option)) {
26
- if (option.some(opt => opt === arg)) {
27
- argvArg = option[0]
28
- }
29
- } else if (option === arg) {
30
- argvArg = option
31
- }
32
- })
33
- if (argvArg) {
34
- lastArg = argvArg
35
- argv[lastArg] = []
36
- if (!pure) {
37
- process.argv[i] = lastArg
38
- }
39
- }
40
- } else {
41
- // this is a value
42
- if (lastArg) {
43
- argv[lastArg].push(arg)
44
- }
45
- }
46
- })
47
-
48
- const [argv1, argv2, ...argvRest] = process.argv
49
-
50
- const entries = Object.entries(def)
51
-
52
- if (entries.length) {
53
- entries.forEach(([k, v]) => {
54
- if (is.empty(argv[k])) {
55
- argv[k] = v
56
-
57
- argvRest.push(k)
58
- if (!is.array(v)) {
59
- v = [v]
60
- }
61
- v.forEach(vv => argvRest.push(vv))
62
- }
63
- })
64
- }
65
-
66
- let argvPrepend = []
67
- if (!is.array(prepend)) {
68
- argvPrepend.push(prepend)
69
- } else if (prepend.length) {
70
- Object.entries(prepend).forEach(([k, v]) => {
71
- argv[k] = v
72
-
73
- argvPrepend.push(k)
74
- if (!is.array(v)) {
75
- argvPrepend.push(v)
76
- } else {
77
- argvPrepend = [...argvPrepend, ...v]
78
- }
79
- })
80
- }
81
-
82
- let argvAppend = []
83
- if (!is.array(append)) {
84
- argvAppend.push(append)
85
- } else if (append.length) {
86
- Object.entries(append)
87
- .forEach(([k, v]) => {
88
- argv[k] = v
89
-
90
- argvAppend.push(k)
91
- if (!is.array(v)) {
92
- argvAppend.push(v)
93
- } else {
94
- argvAppend = [...argvAppend, ...v]
95
- }
96
- })
97
- .filter(a => a)
98
- }
99
-
100
- if (!pure) {
101
- const argvArgs = [argv1, argv2, ...argvAppend, ...argvRest, ...argvPrepend].filter(
102
- a => a && a.length > 0,
103
- )
104
-
105
- process.argv = argvArgs
106
- }
107
-
108
- const args = {}
109
-
110
- Object.entries(argv).map(([k, v]) => {
111
- args[cases.camel(k)] = v
112
- })
113
-
114
- return {
115
- argv,
116
- args,
117
- }
118
- }
package/src/parse/env.mjs DELETED
@@ -1,22 +0,0 @@
1
- import is from '@magic/types'
2
-
3
- export const parseEnv = ({ env = [], pure = false }) => {
4
- const environment = []
5
-
6
- // set env depending on env switches
7
- env
8
- .filter(([argv]) => {
9
- if (!is.array(argv)) {
10
- argv = [argv]
11
- }
12
- return argv.some(a => process.argv.includes(a))
13
- })
14
- .map(([_, key, val]) => {
15
- environment[key] = val
16
- if (!pure) {
17
- process.env[key] = val
18
- }
19
- })
20
-
21
- return environment
22
- }
@@ -1,29 +0,0 @@
1
- import { parseEnv } from './env.mjs'
2
- import { parseArgv } from './argv.mjs'
3
- import { parseCommands } from './commands.mjs'
4
- import { parseRequired } from './required.mjs'
5
- import { clean } from './clean.mjs'
6
-
7
- export const parse = props => {
8
- const { pure = false } = props
9
-
10
- const { pureEnv = pure, pureArgv = pure, pureCommands = pure } = props
11
-
12
- const env = parseEnv({ ...props, pure: pureEnv })
13
- const { argv, args } = parseArgv({ ...props, pure: pureArgv })
14
- const commands = parseCommands({ ...props, pure: pureCommands })
15
-
16
- const cli = {
17
- env,
18
- argv,
19
- args,
20
- commands,
21
- errors: [],
22
- }
23
-
24
- const parsed = clean(cli, props)
25
-
26
- parsed.errors = parseRequired({ props, parsed })
27
-
28
- return parsed
29
- }
@@ -1,32 +0,0 @@
1
- import is from '@magic/types'
2
-
3
- export const parseRequired = (args = {}) => {
4
- const {
5
- parsed,
6
- props: { required },
7
- } = args
8
-
9
- if (is.empty(required)) {
10
- return []
11
- }
12
-
13
- const errors = []
14
-
15
- required.forEach(req => {
16
- if (is.array(req)) {
17
- const some = req.some(a => parsed.argv[a])
18
-
19
- if (!some) {
20
- errors.push(req)
21
- }
22
- } else {
23
- const opt = parsed.argv[req]
24
-
25
- if (is.empty(opt)) {
26
- errors.push(req)
27
- }
28
- }
29
- })
30
-
31
- return errors
32
- }
package/src/spawn.mjs DELETED
@@ -1,12 +0,0 @@
1
- import child_process from 'child_process'
2
-
3
- export const spawn = (cmd, args = [], opt = {}) => {
4
- const opts = {
5
- cwd: process.cwd(),
6
- env: process.env,
7
- stdio: 'inherit',
8
- ...opt,
9
- }
10
-
11
- return child_process.spawn(cmd, args, opts)
12
- }