@percy/cli-command 1.12.0 → 1.14.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/dist/command.js +50 -47
- package/dist/flags.js +8 -4
- package/dist/help.js +77 -72
- package/dist/index.js +2 -2
- package/dist/legacy.js +38 -36
- package/dist/parse.js +123 -122
- package/package.json +5 -5
package/dist/command.js
CHANGED
|
@@ -5,61 +5,68 @@ import { generatePromise, AbortController, AbortError } from '@percy/core/utils'
|
|
|
5
5
|
import * as CoreConfig from '@percy/core/config';
|
|
6
6
|
import * as builtInFlags from './flags.js';
|
|
7
7
|
import formatHelp from './help.js';
|
|
8
|
-
import parse from './parse.js';
|
|
8
|
+
import parse from './parse.js';
|
|
9
9
|
|
|
10
|
+
// Copies a command definition and adds built-in flags and config options.
|
|
10
11
|
function withBuiltIns(definition) {
|
|
11
|
-
let def = {
|
|
12
|
+
let def = {
|
|
13
|
+
...definition
|
|
12
14
|
};
|
|
13
|
-
def.flags = [...(def.flags ?? [])];
|
|
15
|
+
def.flags = [...(def.flags ?? [])];
|
|
14
16
|
|
|
17
|
+
// ensure built-ins aren't already overridden
|
|
15
18
|
let addDedupedFlag = flag => {
|
|
16
19
|
if (!def.flags.find(f => f.name === flag.name)) {
|
|
17
20
|
let short = def.flags.find(f => f.short === flag.short);
|
|
18
|
-
def.flags.push(short ? del({
|
|
21
|
+
def.flags.push(short ? del({
|
|
22
|
+
...flag
|
|
19
23
|
}, 'short') : flag);
|
|
20
24
|
}
|
|
21
|
-
};
|
|
22
|
-
|
|
25
|
+
};
|
|
23
26
|
|
|
27
|
+
// add percy specific built-in flags
|
|
24
28
|
if (def.percy && def.percy !== true) {
|
|
25
|
-
builtInFlags.PERCY.forEach(addDedupedFlag);
|
|
29
|
+
builtInFlags.PERCY.forEach(addDedupedFlag);
|
|
26
30
|
|
|
31
|
+
// maybe include percy server flags
|
|
27
32
|
if (def.percy.server === true) {
|
|
28
33
|
builtInFlags.SERVER.forEach(addDedupedFlag);
|
|
29
|
-
}
|
|
30
|
-
|
|
34
|
+
}
|
|
31
35
|
|
|
36
|
+
// maybe include percy discovery flags
|
|
32
37
|
if (def.percy.skipDiscovery !== true) {
|
|
33
38
|
builtInFlags.DISCOVERY.forEach(addDedupedFlag);
|
|
34
39
|
}
|
|
35
|
-
}
|
|
36
|
-
|
|
40
|
+
}
|
|
37
41
|
|
|
38
|
-
|
|
42
|
+
// always add global built-in flags
|
|
43
|
+
builtInFlags.GLOBAL.forEach(addDedupedFlag);
|
|
39
44
|
|
|
40
|
-
|
|
45
|
+
// copy any existing config before adding to it
|
|
46
|
+
def.config = {
|
|
47
|
+
...def.config
|
|
41
48
|
};
|
|
42
49
|
def.config.schemas = [...(def.config.schemas ?? [])];
|
|
43
|
-
def.config.migrations = [...(def.config.migrations ?? [])];
|
|
50
|
+
def.config.migrations = [...(def.config.migrations ?? [])];
|
|
44
51
|
|
|
52
|
+
// add percy specific built-in config options
|
|
45
53
|
if (def.percy) {
|
|
46
54
|
def.config.schemas.unshift(CoreConfig.schemas);
|
|
47
55
|
def.config.migrations.unshift(CoreConfig.migrations);
|
|
48
56
|
}
|
|
49
|
-
|
|
50
57
|
return def;
|
|
51
|
-
}
|
|
52
|
-
|
|
58
|
+
}
|
|
53
59
|
|
|
60
|
+
// Helper to throw an error with an exit code and optional reason message
|
|
54
61
|
function exit(exitCode, reason = '') {
|
|
55
62
|
let err = reason instanceof Error ? reason : new Error(reason);
|
|
56
63
|
throw Object.assign(err, {
|
|
57
64
|
exitCode
|
|
58
65
|
});
|
|
59
|
-
}
|
|
60
|
-
// and other common command helpers and properties.
|
|
61
|
-
|
|
66
|
+
}
|
|
62
67
|
|
|
68
|
+
// Runs the parsed command callback with a contextual argument consisting of specific parsed input
|
|
69
|
+
// and other common command helpers and properties.
|
|
63
70
|
async function runCommandWithContext(parsed) {
|
|
64
71
|
let {
|
|
65
72
|
command,
|
|
@@ -67,8 +74,8 @@ async function runCommandWithContext(parsed) {
|
|
|
67
74
|
args,
|
|
68
75
|
argv,
|
|
69
76
|
log
|
|
70
|
-
} = parsed;
|
|
71
|
-
|
|
77
|
+
} = parsed;
|
|
78
|
+
// include flags, args, argv, logger, exit helper, and env info
|
|
72
79
|
let context = {
|
|
73
80
|
flags,
|
|
74
81
|
args,
|
|
@@ -78,13 +85,15 @@ async function runCommandWithContext(parsed) {
|
|
|
78
85
|
};
|
|
79
86
|
let env = context.env = process.env;
|
|
80
87
|
let pkg = command.packageInformation;
|
|
81
|
-
let def = command.definition;
|
|
88
|
+
let def = command.definition;
|
|
82
89
|
|
|
90
|
+
// automatically include a preconfigured percy instance
|
|
83
91
|
if (def.percy) {
|
|
84
92
|
let {
|
|
85
93
|
Percy
|
|
86
|
-
} = await import('@percy/core');
|
|
94
|
+
} = await import('@percy/core');
|
|
87
95
|
|
|
96
|
+
// shallow merge with default options
|
|
88
97
|
let conf = {
|
|
89
98
|
server: false,
|
|
90
99
|
...def.percy
|
|
@@ -93,60 +102,55 @@ async function runCommandWithContext(parsed) {
|
|
|
93
102
|
conf.environmentInfo || (conf.environmentInfo = `node/${process.version}`);
|
|
94
103
|
Object.defineProperty(context, 'percy', {
|
|
95
104
|
configurable: true,
|
|
96
|
-
|
|
97
105
|
get() {
|
|
98
106
|
// percy is disabled, do not return an instance
|
|
99
|
-
if (env.PERCY_ENABLE === '0') return;
|
|
107
|
+
if (env.PERCY_ENABLE === '0') return;
|
|
100
108
|
|
|
109
|
+
// redefine the context property once configured
|
|
101
110
|
Object.defineProperty(context, 'percy', {
|
|
102
111
|
// map and merge percy arguments with config options
|
|
103
112
|
value: new Percy([...parsed.operators.entries()].reduce((conf, [opt, value]) => opt.percyrc ? set(conf, opt.percyrc, value) : conf, conf))
|
|
104
113
|
});
|
|
105
114
|
return context.percy;
|
|
106
115
|
}
|
|
107
|
-
|
|
108
116
|
});
|
|
109
|
-
}
|
|
110
|
-
|
|
117
|
+
}
|
|
111
118
|
|
|
119
|
+
// process signals will abort
|
|
112
120
|
let ctrl = new AbortController();
|
|
113
121
|
let signals = ['SIGUSR1', 'SIGUSR2', 'SIGTERM', 'SIGINT', 'SIGHUP'].map(signal => {
|
|
114
122
|
let handler = () => ctrl.abort(new AbortError(signal, {
|
|
115
123
|
signal,
|
|
116
124
|
exitCode: 0
|
|
117
125
|
}));
|
|
118
|
-
|
|
119
126
|
handler.off = () => process.off(signal, handler);
|
|
120
|
-
|
|
121
127
|
process.on(signal, handler);
|
|
122
128
|
return handler;
|
|
123
|
-
});
|
|
129
|
+
});
|
|
124
130
|
|
|
131
|
+
// run the command callback with context and cleanup handlers after
|
|
125
132
|
await generatePromise(command.callback(context), ctrl.signal, error => {
|
|
126
133
|
for (let handler of signals) handler.off();
|
|
127
|
-
|
|
128
134
|
if (error) throw error;
|
|
129
135
|
});
|
|
130
|
-
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Returns a command runner function that when run will parse provided command-line options and run
|
|
131
139
|
// the parsed command callback. The returned runner will automatically output version and help
|
|
132
140
|
// information when requested, and handle any thrown errors exiting when appropriate.
|
|
133
|
-
|
|
134
|
-
|
|
135
141
|
export function command(name, definition, callback) {
|
|
136
|
-
definition = withBuiltIns(definition);
|
|
142
|
+
definition = withBuiltIns(definition);
|
|
137
143
|
|
|
144
|
+
// auto register config schemas and migrations
|
|
138
145
|
PercyConfig.addSchema(definition.config.schemas);
|
|
139
146
|
PercyConfig.addMigration(definition.config.migrations);
|
|
140
|
-
|
|
141
147
|
async function runner(argv = []) {
|
|
142
148
|
// reset loglevel for testing
|
|
143
149
|
logger.loglevel('info');
|
|
144
150
|
let log = logger('cli');
|
|
145
|
-
|
|
146
151
|
try {
|
|
147
152
|
// parse input
|
|
148
153
|
let parsed = await parse(runner, argv);
|
|
149
|
-
|
|
150
154
|
if (parsed.version) {
|
|
151
155
|
// version requested
|
|
152
156
|
log.stdout.write(parsed.command.definition.version + '\n');
|
|
@@ -161,24 +165,23 @@ export function command(name, definition, callback) {
|
|
|
161
165
|
// auto log unhandled error messages
|
|
162
166
|
if (err.message && !err.signal) {
|
|
163
167
|
if (err.exitCode === 0) log.warn(err.message);else log.error(err);
|
|
164
|
-
}
|
|
165
|
-
|
|
168
|
+
}
|
|
166
169
|
|
|
170
|
+
// exit when appropriate
|
|
167
171
|
if (err.exitCode !== 0) {
|
|
168
172
|
err.exitCode ?? (err.exitCode = 1);
|
|
169
173
|
err.message || (err.message = `EEXIT: ${err.exitCode}`);
|
|
170
|
-
|
|
171
174
|
if (definition.exitOnError) {
|
|
172
175
|
process.exit(err.exitCode);
|
|
173
|
-
}
|
|
174
|
-
|
|
176
|
+
}
|
|
175
177
|
|
|
178
|
+
// re-throw when not exiting
|
|
176
179
|
throw err;
|
|
177
180
|
}
|
|
178
181
|
}
|
|
179
|
-
}
|
|
180
|
-
|
|
182
|
+
}
|
|
181
183
|
|
|
184
|
+
// define command meta information
|
|
182
185
|
Object.defineProperties(runner, {
|
|
183
186
|
name: {
|
|
184
187
|
enumerable: true,
|
package/dist/flags.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import logger from '@percy/logger';
|
|
1
|
+
import logger from '@percy/logger';
|
|
2
2
|
|
|
3
|
+
// Global CLI flags
|
|
3
4
|
export const verbose = {
|
|
4
5
|
name: 'verbose',
|
|
5
6
|
description: 'Log everything',
|
|
@@ -23,8 +24,9 @@ export const silent = {
|
|
|
23
24
|
parse: () => !!logger.loglevel('silent'),
|
|
24
25
|
group: 'Global',
|
|
25
26
|
short: 's'
|
|
26
|
-
};
|
|
27
|
+
};
|
|
27
28
|
|
|
29
|
+
// Common percy and asset discovery flags
|
|
28
30
|
export const config = {
|
|
29
31
|
name: 'config',
|
|
30
32
|
description: 'Config file path',
|
|
@@ -89,11 +91,13 @@ export const debug = {
|
|
|
89
91
|
parse: () => !!logger.loglevel('debug'),
|
|
90
92
|
percyrc: 'skipUploads',
|
|
91
93
|
group: 'Percy'
|
|
92
|
-
};
|
|
94
|
+
};
|
|
93
95
|
|
|
96
|
+
// Group constants
|
|
94
97
|
export const GLOBAL = [verbose, quiet, silent];
|
|
95
98
|
export const PERCY = [config, dryRun];
|
|
96
99
|
export const SERVER = [port];
|
|
97
|
-
export const DISCOVERY = [allowedHostnames, disallowedHostnames, networkIdleTimeout, disableCache, debug];
|
|
100
|
+
export const DISCOVERY = [allowedHostnames, disallowedHostnames, networkIdleTimeout, disableCache, debug];
|
|
98
101
|
|
|
102
|
+
// export the namespace as the default
|
|
99
103
|
export * as default from './flags.js';
|
package/dist/help.js
CHANGED
|
@@ -1,30 +1,31 @@
|
|
|
1
|
-
import logger from '@percy/logger';
|
|
1
|
+
import logger from '@percy/logger';
|
|
2
2
|
|
|
3
|
+
// Helper to return an argument's description including it's default value.
|
|
3
4
|
function argumentDescription(arg) {
|
|
4
5
|
let desc = arg.description || '';
|
|
5
|
-
|
|
6
6
|
if (arg.default != null) {
|
|
7
7
|
desc += ` (default: ${JSON.stringify(arg.default)})`;
|
|
8
8
|
}
|
|
9
|
-
|
|
10
9
|
return desc.trim();
|
|
11
|
-
}
|
|
12
|
-
|
|
10
|
+
}
|
|
13
11
|
|
|
12
|
+
// Helper to return an array of visible argument usage-description pairs.
|
|
14
13
|
async function visibleArguments(command) {
|
|
15
|
-
let args = command.definition.args ?? [];
|
|
14
|
+
let args = command.definition.args ?? [];
|
|
16
15
|
|
|
16
|
+
// not hidden and not deprecated
|
|
17
17
|
let visible = args.reduce((visible, arg) => {
|
|
18
18
|
if (arg.hidden || arg.deprecated) return visible;
|
|
19
19
|
visible.push([arg.name, argumentDescription(arg)]);
|
|
20
20
|
return visible;
|
|
21
|
-
}, []);
|
|
21
|
+
}, []);
|
|
22
22
|
|
|
23
|
+
// do not return any arguments if none have descriptions
|
|
23
24
|
if (!visible.some(([, desc]) => !!desc)) return [];
|
|
24
25
|
return visible;
|
|
25
|
-
}
|
|
26
|
-
|
|
26
|
+
}
|
|
27
27
|
|
|
28
|
+
// Helper to return a command's usage string.
|
|
28
29
|
function commandUsage(command) {
|
|
29
30
|
let {
|
|
30
31
|
name,
|
|
@@ -35,70 +36,68 @@ function commandUsage(command) {
|
|
|
35
36
|
args,
|
|
36
37
|
commands
|
|
37
38
|
} = definition;
|
|
38
|
-
|
|
39
39
|
if (!command.callback && commands !== null && commands !== void 0 && commands.length) {
|
|
40
40
|
// if there are subcommands but no callback show subcommand usage
|
|
41
41
|
usage || (usage = '<command>');
|
|
42
42
|
} else {
|
|
43
43
|
var _args, _args2;
|
|
44
|
-
|
|
45
44
|
// show options followed by arguments
|
|
46
45
|
args = (_args = args) === null || _args === void 0 ? void 0 : _args.filter(a => !a.hidden && !a.deprecated).map(a => a.required ? `<${a.name}>` : `[${a.name}]`);
|
|
47
46
|
usage || (usage = `[options] ${((_args2 = args) === null || _args2 === void 0 ? void 0 : _args2.join(' ')) || ''}`);
|
|
48
47
|
}
|
|
49
|
-
|
|
50
48
|
return `${name} ${usage}`.trim();
|
|
51
|
-
}
|
|
52
|
-
|
|
49
|
+
}
|
|
53
50
|
|
|
51
|
+
// Helper to return an array of visible command usage-description pairs.
|
|
54
52
|
async function visibleCommands(command, nested) {
|
|
55
53
|
let commands = command.definition.commands ?? [];
|
|
56
54
|
if (typeof commands === 'function') commands = await commands();
|
|
57
55
|
let visible = [];
|
|
58
|
-
|
|
59
56
|
for (let cmd of commands) {
|
|
60
57
|
// skip hidden commands entirely
|
|
61
|
-
if (cmd.definition.hidden) continue;
|
|
58
|
+
if (cmd.definition.hidden) continue;
|
|
62
59
|
|
|
60
|
+
// create parent references and format subcommand names
|
|
63
61
|
let name = command.parent ? `${command.name}:${cmd.name}` : cmd.name;
|
|
64
|
-
cmd = {
|
|
62
|
+
cmd = {
|
|
63
|
+
...cmd,
|
|
65
64
|
name,
|
|
66
65
|
parent: command
|
|
67
|
-
};
|
|
66
|
+
};
|
|
68
67
|
|
|
68
|
+
// commands without callbacks are not visible themselves
|
|
69
69
|
if (cmd.callback) {
|
|
70
70
|
let {
|
|
71
71
|
description
|
|
72
72
|
} = cmd.definition;
|
|
73
73
|
visible.push([commandUsage(cmd), description]);
|
|
74
|
-
}
|
|
75
|
-
|
|
74
|
+
}
|
|
76
75
|
|
|
76
|
+
// include visible subcommands, but not deeply nested subcommands
|
|
77
77
|
if (!nested && cmd.definition.commands) {
|
|
78
78
|
visible.push(...(await visibleCommands(cmd, true)));
|
|
79
79
|
}
|
|
80
|
-
}
|
|
81
|
-
|
|
80
|
+
}
|
|
82
81
|
|
|
82
|
+
// include implicit help if there are visible commands
|
|
83
83
|
if (!nested && visible.length) {
|
|
84
84
|
visible.push(['help [command]', 'Display command help']);
|
|
85
85
|
}
|
|
86
|
-
|
|
87
86
|
return visible;
|
|
88
|
-
}
|
|
89
|
-
|
|
87
|
+
}
|
|
90
88
|
|
|
89
|
+
// Helper to return an flags's description including it's default value.
|
|
91
90
|
function flagDescription(flag) {
|
|
92
|
-
let desc = flag.description || '';
|
|
91
|
+
let desc = flag.description || '';
|
|
93
92
|
|
|
93
|
+
// a boolean that defaults to false will be displayed with a --no prefix
|
|
94
94
|
if (flag.type && flag.type !== 'boolean' && flag.default != null) {
|
|
95
95
|
desc += ` (default: ${JSON.stringify(flag.default)})`;
|
|
96
96
|
}
|
|
97
|
-
|
|
98
97
|
return desc.trim();
|
|
99
|
-
}
|
|
100
|
-
|
|
98
|
+
}
|
|
101
99
|
|
|
100
|
+
// Helper to return a flag's usage string.
|
|
102
101
|
export function flagUsage(flag) {
|
|
103
102
|
let {
|
|
104
103
|
short,
|
|
@@ -106,33 +105,34 @@ export function flagUsage(flag) {
|
|
|
106
105
|
type
|
|
107
106
|
} = flag;
|
|
108
107
|
let term = flag.name ? `--${flag.name}` : '';
|
|
109
|
-
|
|
110
108
|
if (type && type !== 'boolean') {
|
|
111
109
|
// if there is a default it is not required
|
|
112
110
|
usage || (usage = flag.default != null ? `[${type}]` : `<${type}>`);
|
|
113
111
|
} else if (flag.default === true) {
|
|
114
112
|
// negated booleans have a prefix
|
|
115
113
|
term && (term = `--no-${flag.name}`);
|
|
116
|
-
}
|
|
117
|
-
|
|
114
|
+
}
|
|
118
115
|
|
|
116
|
+
// add short flag usage
|
|
119
117
|
if (short) term = `-${short}${term ? `, ${term}` : ''}`;
|
|
120
118
|
return `${term} ${usage || ''}`.trim();
|
|
121
|
-
}
|
|
122
|
-
// with the returned pair representing any group the flag should belong to.
|
|
119
|
+
}
|
|
123
120
|
|
|
121
|
+
// Helper to return an array of visible flag usage-description pairs. A third entry is also included
|
|
122
|
+
// with the returned pair representing any group the flag should belong to.
|
|
124
123
|
async function visibleFlags(command) {
|
|
125
|
-
let flags = command.definition.flags;
|
|
124
|
+
let flags = command.definition.flags;
|
|
126
125
|
|
|
126
|
+
// not hidden and not deprecated
|
|
127
127
|
let visible = flags.reduce((visible, flag) => {
|
|
128
128
|
if (flag.hidden || flag.deprecated) return visible;
|
|
129
129
|
visible.push([flagUsage(flag), flagDescription(flag), flag.group]);
|
|
130
130
|
return visible;
|
|
131
|
-
}, []);
|
|
131
|
+
}, []);
|
|
132
132
|
|
|
133
|
+
// maybe hide the implicit help long or short flag (or both)
|
|
133
134
|
let showLongHelpFlag = !flags.some(f => f.name === 'help');
|
|
134
135
|
let showShortHelpFlag = !flags.some(f => f.short === 'h');
|
|
135
|
-
|
|
136
136
|
if (showLongHelpFlag || showShortHelpFlag) {
|
|
137
137
|
let help = {
|
|
138
138
|
name: 'help',
|
|
@@ -142,12 +142,10 @@ async function visibleFlags(command) {
|
|
|
142
142
|
if (!showShortHelpFlag) delete help.short;
|
|
143
143
|
visible.push([flagUsage(help), 'Display command help', 'Global']);
|
|
144
144
|
}
|
|
145
|
-
|
|
146
145
|
if (command.definition.version) {
|
|
147
146
|
// maybe hide the implicit version long or short flag (or both)
|
|
148
147
|
let showLongVerFlag = !flags.some(f => f.name === 'version');
|
|
149
148
|
let showShortVerFlag = !flags.some(f => f.short === 'V');
|
|
150
|
-
|
|
151
149
|
if (showLongVerFlag || showShortVerFlag) {
|
|
152
150
|
let ver = {
|
|
153
151
|
name: 'version',
|
|
@@ -158,33 +156,31 @@ async function visibleFlags(command) {
|
|
|
158
156
|
visible.push([flagUsage(ver), 'Display version', 'Global']);
|
|
159
157
|
}
|
|
160
158
|
}
|
|
161
|
-
|
|
162
159
|
return visible;
|
|
163
|
-
}
|
|
164
|
-
|
|
160
|
+
}
|
|
165
161
|
|
|
162
|
+
// Helper to wrap a string at a length by inserting a newline between every segment.
|
|
166
163
|
function wrapString(str, len) {
|
|
167
164
|
let regex = new RegExp(`.{1,${len - 1}}(\\s|$)|\\S+?(\\s|$)`, 'g');
|
|
168
165
|
return str.replace(regex, s => s.replace(/\s*\n?$/, str.endsWith(s) ? '' : '\n'));
|
|
169
|
-
}
|
|
170
|
-
// wraps within the available space beside the usage string.
|
|
171
|
-
|
|
166
|
+
}
|
|
172
167
|
|
|
168
|
+
// Helper to format an option usage-description pair into a single string where the description
|
|
169
|
+
// wraps within the available space beside the usage string.
|
|
173
170
|
function formatOptionHelp(option, usageWidth, maxWidth) {
|
|
174
|
-
if (!option[1]) return option[0];
|
|
175
|
-
|
|
176
|
-
let descWidth = Math.max(maxWidth - usageWidth, 40);
|
|
177
|
-
|
|
171
|
+
if (!option[1]) return option[0];
|
|
172
|
+
// calculate the available description width, at minimum 40
|
|
173
|
+
let descWidth = Math.max(maxWidth - usageWidth, 40);
|
|
174
|
+
// pad usage up to the description and indent the description
|
|
178
175
|
return option[0].padEnd(usageWidth) + wrapString(option[1], descWidth).replace(/\n+/gm, `$&${' '.repeat(usageWidth)}`);
|
|
179
|
-
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Returns a formatted string displaying help output for the specified command. The help output
|
|
180
179
|
// total width is determined by the logger's stdout TTY columns. Help output includes the command
|
|
181
180
|
// description, usage, arguments, subcommand usage and descriptions, flag and flag group usage and
|
|
182
181
|
// descriptions, and any command examples.
|
|
183
|
-
|
|
184
|
-
|
|
185
182
|
export async function formatHelp(command) {
|
|
186
183
|
var _examples, _examples2;
|
|
187
|
-
|
|
188
184
|
let {
|
|
189
185
|
name,
|
|
190
186
|
parent: top,
|
|
@@ -194,52 +190,61 @@ export async function formatHelp(command) {
|
|
|
194
190
|
description,
|
|
195
191
|
examples
|
|
196
192
|
} = definition;
|
|
197
|
-
let output = [];
|
|
193
|
+
let output = [];
|
|
198
194
|
|
|
195
|
+
// collect visible items first to determine max usage width
|
|
199
196
|
let args = await visibleArguments(command);
|
|
200
197
|
let commands = await visibleCommands(command);
|
|
201
|
-
let flags = await visibleFlags(command);
|
|
198
|
+
let flags = await visibleFlags(command);
|
|
202
199
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
let formatList = items => items.map(item => Array.isArray(item) ? // indent items and separate columns by 2 spaces each
|
|
208
|
-
formatOptionHelp(item, usageWidth + 2, maxWidth - 2) : item).join('\n').replace(/^(?!$)/gm, ' '.repeat(2)); // format the command name to include the topmost command name
|
|
200
|
+
// maximum help output total width
|
|
201
|
+
let maxWidth = logger.stdout.columns || 100;
|
|
202
|
+
// calculate the longest usage width
|
|
203
|
+
let usageWidth = Math.max(args.reduce((max, [usage]) => Math.max(max, usage.length), 0), commands.reduce((max, [usage]) => Math.max(max, usage.length), 0), flags.reduce((max, [usage]) => Math.max(max, usage.length), 0));
|
|
209
204
|
|
|
205
|
+
// format lists of options and add appropriate indentation
|
|
206
|
+
let formatList = items => items.map(item => Array.isArray(item) ?
|
|
207
|
+
// indent items and separate columns by 2 spaces each
|
|
208
|
+
formatOptionHelp(item, usageWidth + 2, maxWidth - 2) : item).join('\n').replace(/^(?!$)/gm, ' '.repeat(2));
|
|
210
209
|
|
|
210
|
+
// format the command name to include the topmost command name
|
|
211
211
|
while ((_top = top) !== null && _top !== void 0 && _top.parent) {
|
|
212
212
|
var _top;
|
|
213
|
-
|
|
214
213
|
top = top.parent;
|
|
215
214
|
}
|
|
216
|
-
|
|
217
215
|
if (top) name = `${top.name} ${name}`;
|
|
218
|
-
command = {
|
|
216
|
+
command = {
|
|
217
|
+
...command,
|
|
219
218
|
name
|
|
220
|
-
};
|
|
219
|
+
};
|
|
221
220
|
|
|
221
|
+
// include command description and own usage
|
|
222
222
|
if (description) output.push(wrapString(description, maxWidth), '');
|
|
223
|
-
output.push('Usage:', formatList([`$ ${commandUsage(command)}`]), '');
|
|
223
|
+
output.push('Usage:', formatList([`$ ${commandUsage(command)}`]), '');
|
|
224
224
|
|
|
225
|
-
|
|
225
|
+
// include command arguments
|
|
226
|
+
if (args.length) output.push('Arguments:', formatList(args), '');
|
|
226
227
|
|
|
228
|
+
// include command subcommands
|
|
227
229
|
let commandGroupName = command.parent ? 'Subcommands:' : 'Commands:';
|
|
228
|
-
if (commands.length) output.push(commandGroupName, formatList(commands), '');
|
|
230
|
+
if (commands.length) output.push(commandGroupName, formatList(commands), '');
|
|
229
231
|
|
|
232
|
+
// separate flags into groups
|
|
230
233
|
let groups = flags.reduce((all, option) => {
|
|
231
234
|
let group = option[2] ? `${option[2]} options` : 'Options';
|
|
232
235
|
return all.set(group, [...(all.get(group) || []), option]);
|
|
233
|
-
}, new Map([['Options', []]]));
|
|
236
|
+
}, new Map([['Options', []]]));
|
|
234
237
|
|
|
238
|
+
// include each group of command flags
|
|
235
239
|
for (let [group, flags] of groups.entries()) {
|
|
236
240
|
if (flags.length) output.push(`${group}:`, formatList(flags), '');
|
|
237
|
-
}
|
|
238
|
-
|
|
241
|
+
}
|
|
239
242
|
|
|
243
|
+
// include command examples and replace '$0' with the command name
|
|
240
244
|
examples = (_examples = examples) === null || _examples === void 0 ? void 0 : _examples.map(ex => ex.replace('$0', `$ ${name}`));
|
|
241
|
-
if ((_examples2 = examples) !== null && _examples2 !== void 0 && _examples2.length) output.push('Examples:', formatList(examples), '');
|
|
245
|
+
if ((_examples2 = examples) !== null && _examples2 !== void 0 && _examples2.length) output.push('Examples:', formatList(examples), '');
|
|
242
246
|
|
|
247
|
+
// join each line of output
|
|
243
248
|
return output.join('\n');
|
|
244
249
|
}
|
|
245
250
|
export default formatHelp;
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { default, command } from './command.js';
|
|
2
|
-
export { legacyCommand, legacyFlags as flags } from './legacy.js';
|
|
3
|
-
|
|
2
|
+
export { legacyCommand, legacyFlags as flags } from './legacy.js';
|
|
3
|
+
// export common packages to avoid dependency resolution issues
|
|
4
4
|
export { default as PercyConfig } from '@percy/config';
|
|
5
5
|
export { default as logger } from '@percy/logger';
|
package/dist/legacy.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { merge } from '@percy/config/utils';
|
|
2
|
-
import { command } from './command.js';
|
|
2
|
+
import { command } from './command.js';
|
|
3
3
|
|
|
4
|
+
// Legacy flags for older commands that inadvertently import a newer @percy/cli-command
|
|
4
5
|
export const legacyFlags = {
|
|
5
6
|
config: {},
|
|
6
7
|
logging: {},
|
|
@@ -19,34 +20,36 @@ export const legacyFlags = {
|
|
|
19
20
|
type: 'integer',
|
|
20
21
|
...def
|
|
21
22
|
})
|
|
22
|
-
};
|
|
23
|
+
};
|
|
23
24
|
|
|
25
|
+
// Maps a legacy arg that might have alternate properties
|
|
24
26
|
function legacyArg(legacy) {
|
|
25
27
|
let {
|
|
26
28
|
name,
|
|
27
29
|
...def
|
|
28
|
-
} = legacy;
|
|
29
|
-
|
|
30
|
-
def.attribute = name;
|
|
31
|
-
|
|
30
|
+
} = legacy;
|
|
31
|
+
// do not camelcase legacy attribute names
|
|
32
|
+
def.attribute = name;
|
|
33
|
+
// add name back to mapped options
|
|
32
34
|
return {
|
|
33
35
|
name,
|
|
34
36
|
...def
|
|
35
37
|
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
+
}
|
|
38
39
|
|
|
40
|
+
// Maps a legacy flag that might have alternate properties
|
|
39
41
|
function legacyFlag(name, legacy) {
|
|
40
42
|
let {
|
|
41
43
|
char: short,
|
|
42
44
|
deprecated,
|
|
43
45
|
...def
|
|
44
|
-
} = legacy;
|
|
45
|
-
|
|
46
|
-
if (def.type === 'option') def.type = 'value';
|
|
47
|
-
|
|
48
|
-
def.attribute = name;
|
|
46
|
+
} = legacy;
|
|
47
|
+
// generic legacy type looks odd in help output
|
|
48
|
+
if (def.type === 'option') def.type = 'value';
|
|
49
|
+
// do not camelcase legacy attribute names
|
|
50
|
+
def.attribute = name;
|
|
49
51
|
|
|
52
|
+
// transform legacy deprecated syntax
|
|
50
53
|
if (deprecated) {
|
|
51
54
|
let {
|
|
52
55
|
until,
|
|
@@ -54,20 +57,19 @@ function legacyFlag(name, legacy) {
|
|
|
54
57
|
alt
|
|
55
58
|
} = deprecated !== true ? deprecated : {};
|
|
56
59
|
def.deprecated = [until, map ? `--${map}` : alt];
|
|
57
|
-
}
|
|
58
|
-
|
|
60
|
+
}
|
|
59
61
|
|
|
62
|
+
// add name and mapped short option
|
|
60
63
|
return {
|
|
61
64
|
name,
|
|
62
65
|
short,
|
|
63
66
|
...def
|
|
64
67
|
};
|
|
65
|
-
}
|
|
66
|
-
|
|
68
|
+
}
|
|
67
69
|
|
|
70
|
+
// Maps a legacy class-based command to the new functional syntax
|
|
68
71
|
export function legacyCommand(name, constructor) {
|
|
69
72
|
var _constructor$args, _constructor$flags;
|
|
70
|
-
|
|
71
73
|
let runner = command(name, {
|
|
72
74
|
description: constructor.description,
|
|
73
75
|
examples: constructor.examples,
|
|
@@ -81,23 +83,26 @@ export function legacyCommand(name, constructor) {
|
|
|
81
83
|
legacy: true
|
|
82
84
|
}, async function* (ctx) {
|
|
83
85
|
let instance = new constructor();
|
|
84
|
-
let error;
|
|
86
|
+
let error;
|
|
85
87
|
|
|
88
|
+
// not an instance of the constructor, create one ourself
|
|
86
89
|
if (!(instance instanceof constructor)) {
|
|
87
90
|
instance = Object.create(constructor.prototype);
|
|
88
|
-
}
|
|
89
|
-
|
|
91
|
+
}
|
|
90
92
|
|
|
91
|
-
|
|
93
|
+
// alias legacy global flags attribute names when necessary
|
|
94
|
+
if (ctx.flags.dryRun) ctx.flags['dry-run'] = ctx.flags.dryRun;
|
|
92
95
|
|
|
96
|
+
// legacy percyrc value
|
|
93
97
|
let percyrc = overrides => merge([ctx.percy.config, overrides, {
|
|
94
98
|
skipUploads: ctx.flags.debug,
|
|
95
99
|
dryRun: ctx.flags['dry-run'],
|
|
96
100
|
config: false
|
|
97
|
-
}], (path, prev, next) =>
|
|
98
|
-
|
|
99
|
-
|
|
101
|
+
}], (path, prev, next) =>
|
|
102
|
+
// do not merge arrays
|
|
103
|
+
Array.isArray(next) && [path, next]);
|
|
100
104
|
|
|
105
|
+
// augment legacy instance properties
|
|
101
106
|
Object.defineProperties(instance, {
|
|
102
107
|
isPercyEnabled: {
|
|
103
108
|
value: () => !!ctx.percy
|
|
@@ -123,39 +128,36 @@ export function legacyCommand(name, constructor) {
|
|
|
123
128
|
log: {
|
|
124
129
|
value: ctx.log
|
|
125
130
|
}
|
|
126
|
-
});
|
|
131
|
+
});
|
|
127
132
|
|
|
133
|
+
// legacy signal handling
|
|
128
134
|
let signals = ['SIGHUP', 'SIGINT', 'SIGTERM'];
|
|
129
|
-
|
|
130
135
|
let cleanup = () => {
|
|
131
136
|
var _instance$finally, _instance;
|
|
132
|
-
|
|
133
137
|
return (_instance$finally = (_instance = instance).finally) === null || _instance$finally === void 0 ? void 0 : _instance$finally.call(_instance, new Error('SIGTERM'));
|
|
134
138
|
};
|
|
139
|
+
for (let e of signals) process.on(e, cleanup);
|
|
135
140
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
141
|
+
// run and handle legacy command
|
|
139
142
|
try {
|
|
140
143
|
yield instance.run();
|
|
141
144
|
} catch (err) {
|
|
142
145
|
throw error = err;
|
|
143
146
|
} finally {
|
|
144
147
|
var _instance$finally2, _instance2;
|
|
145
|
-
|
|
146
148
|
await ((_instance$finally2 = (_instance2 = instance).finally) === null || _instance$finally2 === void 0 ? void 0 : _instance$finally2.call(_instance2, error));
|
|
147
|
-
|
|
148
149
|
for (let e of signals) process.off(e, cleanup);
|
|
149
150
|
}
|
|
150
|
-
});
|
|
151
|
+
});
|
|
151
152
|
|
|
153
|
+
// throw legacy errors for test compatibility
|
|
152
154
|
return Object.defineProperties(args => runner(args).catch(e => {
|
|
153
155
|
throw new Error(`EEXIT: ${e.exitCode}`);
|
|
154
156
|
}), Object.getOwnPropertyDescriptors(runner));
|
|
155
|
-
}
|
|
157
|
+
}
|
|
156
158
|
|
|
159
|
+
// Support the legacy static run method
|
|
157
160
|
command.run = function run(args) {
|
|
158
161
|
return legacyCommand('legacy', this)(args);
|
|
159
162
|
};
|
|
160
|
-
|
|
161
163
|
export default legacyCommand;
|
package/dist/parse.js
CHANGED
|
@@ -1,194 +1,195 @@
|
|
|
1
1
|
import logger from '@percy/logger';
|
|
2
2
|
import { camelcase } from '@percy/config/utils';
|
|
3
|
-
import { flagUsage } from './help.js';
|
|
3
|
+
import { flagUsage } from './help.js';
|
|
4
4
|
|
|
5
|
+
// Make it possible to identify parse errors.
|
|
5
6
|
export class ParseError extends Error {
|
|
6
7
|
name = this.constructor.name;
|
|
7
|
-
}
|
|
8
|
+
}
|
|
8
9
|
|
|
10
|
+
// Helper that makes it possible to throw parse errors without throw or new keywords.
|
|
9
11
|
function parseError(msg) {
|
|
10
12
|
throw new ParseError(msg);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
+
}
|
|
13
14
|
|
|
15
|
+
// Helper to identify a potential command-line option as a flag.
|
|
14
16
|
function isFlag(option) {
|
|
15
17
|
// not a flag if it doesn't start with a dash
|
|
16
|
-
if (!option.startsWith('-')) return false;
|
|
17
|
-
|
|
18
|
-
if (!option.includes(' ')) return true;
|
|
19
|
-
|
|
20
|
-
if (!option.includes('=')) return false;
|
|
21
|
-
|
|
18
|
+
if (!option.startsWith('-')) return false;
|
|
19
|
+
// safe to say it's a flag without a space
|
|
20
|
+
if (!option.includes(' ')) return true;
|
|
21
|
+
// containing a space with no operator is not a proper flag
|
|
22
|
+
if (!option.includes('=')) return false;
|
|
23
|
+
// as long as the operator precedes the space, it is a flag
|
|
22
24
|
return option.indexOf('=') < option.indexOf(' ');
|
|
23
|
-
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Parses input to identify a command. Consumes input and adds to parsed output as needed, returning
|
|
24
28
|
// true if any defined command was found. A command is only identified via leading unparsed input
|
|
25
29
|
// options. If a command is found, parsed output is updated accordingly and the current input option
|
|
26
30
|
// is removed from further processing. An implicit help command is also supported.
|
|
27
|
-
|
|
28
|
-
|
|
29
31
|
async function maybeParseCommand(input, parsed) {
|
|
30
32
|
var _input$;
|
|
31
|
-
|
|
32
33
|
// a flag, no defined commands, or already parsed other options
|
|
33
34
|
if (isFlag(input[0]) || !parsed.command.definition.commands || parsed.operators.size || parsed.argv.length) {
|
|
34
35
|
return;
|
|
35
|
-
}
|
|
36
|
-
|
|
36
|
+
}
|
|
37
37
|
|
|
38
|
+
// implicit help
|
|
38
39
|
if (input[0] === 'help') {
|
|
39
40
|
parsed.help = true;
|
|
40
41
|
input.shift();
|
|
41
|
-
}
|
|
42
|
-
|
|
42
|
+
}
|
|
43
43
|
|
|
44
|
+
// nested commands can be a single argument with command names separated by colons (:)
|
|
44
45
|
let names = ((_input$ = input[0]) === null || _input$ === void 0 ? void 0 : _input$.split(':')) ?? [];
|
|
45
46
|
let command;
|
|
46
|
-
|
|
47
47
|
for (let name of names) {
|
|
48
48
|
var _parent$definition$co, _command;
|
|
49
|
+
let parent = command || parsed.command;
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
// find any nested command by name
|
|
52
52
|
command = (_parent$definition$co = parent.definition.commands) === null || _parent$definition$co === void 0 ? void 0 : _parent$definition$co.find(cmd => cmd.name === name);
|
|
53
|
-
if (!command) break;
|
|
53
|
+
if (!command) break;
|
|
54
54
|
|
|
55
|
+
// maintain nested names and parent relationships
|
|
55
56
|
name = parent.parent ? `${parent.name}:${command.name}` : command.name;
|
|
56
57
|
command = await normalizeCommand(command, {
|
|
57
58
|
name,
|
|
58
59
|
parent
|
|
59
|
-
});
|
|
60
|
+
});
|
|
60
61
|
|
|
62
|
+
// some hidden commands should produce warnings when used
|
|
61
63
|
if (typeof ((_command = command) === null || _command === void 0 ? void 0 : _command.definition.hidden) === 'string') {
|
|
62
64
|
logger('cli').warn(`\nWarning: ${command.definition.hidden}\n`);
|
|
63
65
|
}
|
|
64
66
|
}
|
|
65
|
-
|
|
66
67
|
if (command) {
|
|
67
68
|
// update parsed properties and discard current input
|
|
68
69
|
parsed.log = logger(`cli:${command.name}`);
|
|
69
70
|
parsed.command = command;
|
|
70
71
|
input.shift();
|
|
71
|
-
}
|
|
72
|
-
|
|
72
|
+
}
|
|
73
73
|
|
|
74
|
+
// help was requested, drain remaining input
|
|
74
75
|
if (parsed.help) {
|
|
75
76
|
input.splice(0, input.length);
|
|
76
77
|
}
|
|
77
|
-
|
|
78
78
|
return parsed.help || !!command;
|
|
79
|
-
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Parses input to identify a flag option. Consumes input and adds to parsed output as needed,
|
|
80
82
|
// returning true if any defined flag was found. If a found flag is missing a required argument, the
|
|
81
83
|
// next input option will be consumed as that argument. Boolean flags support negation if their
|
|
82
84
|
// default value is true, integer flags are automatically parsed, and flags that can be provided
|
|
83
85
|
// multiple times will be concatenated together. Implicit help and version flags are also supported.
|
|
84
|
-
|
|
85
|
-
|
|
86
86
|
async function maybeParseFlag(input, parsed) {
|
|
87
87
|
var _value;
|
|
88
|
+
if (!isFlag(input[0])) return;
|
|
88
89
|
|
|
89
|
-
|
|
90
|
-
|
|
90
|
+
// end of options and arguments, consume remaining input
|
|
91
91
|
if (input[0] === '--') {
|
|
92
92
|
let [, ...argv] = input.splice(0, input.length);
|
|
93
93
|
parsed.argv.push(...argv);
|
|
94
94
|
return true;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
let short = input[0][1] !== '-' && input[0][1]; // get any value set with `=` and remove any `=` from short values
|
|
95
|
+
}
|
|
99
96
|
|
|
97
|
+
// a short flag was provided
|
|
98
|
+
let short = input[0][1] !== '-' && input[0][1];
|
|
99
|
+
// get any value set with `=` and remove any `=` from short values
|
|
100
100
|
let [name, value] = short ? ['', input[0].substr(2) || null] : input[0].split('=');
|
|
101
|
-
value = short && (_value = value) !== null && _value !== void 0 && _value.startsWith('=') ? value.substr(1) : value;
|
|
101
|
+
value = short && (_value = value) !== null && _value !== void 0 && _value.startsWith('=') ? value.substr(1) : value;
|
|
102
102
|
|
|
103
|
+
// the flag was negated
|
|
103
104
|
let negated = name.startsWith('--no-');
|
|
104
|
-
name = name.replace(/^--(no-)?/, '');
|
|
105
|
+
name = name.replace(/^--(no-)?/, '');
|
|
105
106
|
|
|
107
|
+
// search for the provided short or long flag
|
|
106
108
|
let flag = parsed.command.definition.flags.find(f => {
|
|
107
109
|
return short ? f.short === short : f.name === name;
|
|
108
110
|
});
|
|
109
|
-
|
|
110
111
|
if (flag) {
|
|
111
112
|
// discard current input
|
|
112
113
|
input.shift();
|
|
113
|
-
|
|
114
114
|
if (!flag.type || flag.type === 'boolean') {
|
|
115
115
|
// short boolean flags can be provided with other short flags
|
|
116
|
-
if (short && value) input.unshift(`-${value}`);
|
|
116
|
+
if (short && value) input.unshift(`-${value}`);
|
|
117
117
|
|
|
118
|
+
// a value was provided to a boolean flag
|
|
118
119
|
if (!short && value) {
|
|
119
120
|
parseError(`Unexpected option argument for '${flagUsage(flag)}'`);
|
|
120
|
-
}
|
|
121
|
-
|
|
121
|
+
}
|
|
122
122
|
|
|
123
|
+
// a boolean value is based on if it was negated
|
|
123
124
|
value = !negated;
|
|
124
125
|
} else if (value == null) {
|
|
125
126
|
// expected a value for a non-boolean flag
|
|
126
127
|
if (!input.length || isFlag(input[0])) {
|
|
127
128
|
parseError(`Missing option argument for '${flagUsage(flag)}'`);
|
|
128
|
-
}
|
|
129
|
-
|
|
129
|
+
}
|
|
130
130
|
|
|
131
|
+
// the value is actually the next input option
|
|
131
132
|
value = input.shift();
|
|
132
|
-
}
|
|
133
|
-
|
|
133
|
+
}
|
|
134
134
|
|
|
135
|
+
// parse the value according to flag properties
|
|
135
136
|
if (flag.type === 'integer') value = parseInt(value, 10);
|
|
136
137
|
if (flag.parse) value = await flag.parse(value);
|
|
137
|
-
|
|
138
138
|
if (flag.multiple) {
|
|
139
139
|
value = (parsed.operators.get(flag) ?? []).concat(value);
|
|
140
|
-
}
|
|
141
|
-
|
|
140
|
+
}
|
|
142
141
|
|
|
142
|
+
// add this flag to the set of parsed operators
|
|
143
143
|
parsed.operators.set(flag, value);
|
|
144
144
|
return true;
|
|
145
|
-
}
|
|
146
|
-
|
|
145
|
+
}
|
|
147
146
|
|
|
147
|
+
// implicit version and help support
|
|
148
148
|
if (parsed.command.definition.version && (name === 'version' || short === 'V')) {
|
|
149
149
|
parsed.version = true;
|
|
150
150
|
} else if (name === 'help' || short === 'h') {
|
|
151
151
|
parsed.help = true;
|
|
152
|
-
}
|
|
153
|
-
|
|
152
|
+
}
|
|
154
153
|
|
|
154
|
+
// help or version was requested, drain remaining input
|
|
155
155
|
if (parsed.help || parsed.version) {
|
|
156
156
|
input.splice(0, input.length);
|
|
157
157
|
return true;
|
|
158
158
|
}
|
|
159
|
-
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Parses input to identify an argument option. Consumes input and adds to parsed output as needed,
|
|
160
162
|
// returning true if any defined argument was found. Arguments are matched according to their
|
|
161
163
|
// respective index and command-line position.
|
|
162
|
-
|
|
163
|
-
|
|
164
164
|
async function maybeParseArg(input, parsed) {
|
|
165
165
|
var _parsed$command$defin;
|
|
166
|
+
if (isFlag(input[0])) return;
|
|
166
167
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
let index = parsed.__args_index__ ?? (parsed.__args_index__ = 0); // there are arguments left to parse
|
|
168
|
+
// the argument index needs to be tracked since flag options can precede argument options
|
|
169
|
+
let index = parsed.__args_index__ ?? (parsed.__args_index__ = 0);
|
|
170
170
|
|
|
171
|
+
// there are arguments left to parse
|
|
171
172
|
if (index < ((_parsed$command$defin = parsed.command.definition.args) === null || _parsed$command$defin === void 0 ? void 0 : _parsed$command$defin.length)) {
|
|
172
173
|
parsed.__args_index__ += 1;
|
|
173
174
|
let value = input.shift();
|
|
174
175
|
let arg = parsed.command.definition.args[index];
|
|
175
|
-
if (arg.parse) value = await arg.parse(value);
|
|
176
|
+
if (arg.parse) value = await arg.parse(value);
|
|
176
177
|
|
|
178
|
+
// add this arg to the set of parsed operators
|
|
177
179
|
parsed.operators.set(arg, value);
|
|
178
180
|
return true;
|
|
179
181
|
}
|
|
180
|
-
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Parses input to identify unknown options. Consumes input and adds to parsed output as needed,
|
|
181
185
|
// returning true when unknown options are found. By default, unknown options result in an error
|
|
182
186
|
// unless the command is defined to accept loose options. If `loose` is a string, it will be logged
|
|
183
187
|
// as a warning when unknown options are found. When the first unknown option is found, all
|
|
184
188
|
// remaining options are also considered unknown.
|
|
185
|
-
|
|
186
|
-
|
|
187
189
|
async function maybeParseUnknown(input, parsed) {
|
|
188
190
|
let {
|
|
189
191
|
loose
|
|
190
192
|
} = parsed.command.definition;
|
|
191
|
-
|
|
192
193
|
if (!loose) {
|
|
193
194
|
// throw a more descriptive error message
|
|
194
195
|
let reason = isFlag(input[0]) ? 'Unknown option' : 'Unexpected argument';
|
|
@@ -196,43 +197,42 @@ async function maybeParseUnknown(input, parsed) {
|
|
|
196
197
|
} else if (typeof loose === 'string') {
|
|
197
198
|
// warn when loose is a string
|
|
198
199
|
parsed.log.warn(loose);
|
|
199
|
-
}
|
|
200
|
-
|
|
200
|
+
}
|
|
201
201
|
|
|
202
|
+
// consume all remaining unknown input options
|
|
202
203
|
let unknown = input.splice(0, input.length);
|
|
203
204
|
parsed.argv.push(...unknown);
|
|
204
205
|
return true;
|
|
205
|
-
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Parses an option's value defined by an environment variable. Adds to parsed output as needed,
|
|
206
209
|
// returning true when the option's value was parsed from the environment. Boolean environment
|
|
207
210
|
// variables will be interpreted as true unless "false" or "0", and integer environment variables
|
|
208
211
|
// will be parsed automatically.
|
|
209
|
-
|
|
210
|
-
|
|
211
212
|
async function maybeParseEnv(option, parsed) {
|
|
212
213
|
// no env option, already provided, or no defined env var
|
|
213
214
|
if (!option.env || parsed.operators.has(option) || !(option.env in process.env)) {
|
|
214
215
|
return;
|
|
215
|
-
}
|
|
216
|
-
|
|
216
|
+
}
|
|
217
217
|
|
|
218
|
+
// get the value from the process env
|
|
218
219
|
let value = process.env[option.env];
|
|
219
220
|
let flag = parsed.command.definition.flags.includes(option);
|
|
220
|
-
|
|
221
221
|
if (flag && (!option.type || option.type === 'boolean')) {
|
|
222
222
|
// parse booleans as not "false" or not "0"
|
|
223
223
|
value = value !== 'false' && value !== '0';
|
|
224
224
|
} else if (option.type === 'integer') {
|
|
225
225
|
// parse integer option values
|
|
226
226
|
value = parseInt(value, 10);
|
|
227
|
-
}
|
|
228
|
-
|
|
227
|
+
}
|
|
229
228
|
|
|
229
|
+
// custom parse and add this option to the set of parsed operators
|
|
230
230
|
if (option.parse) value = await option.parse(value);
|
|
231
231
|
parsed.operators.set(option, value);
|
|
232
232
|
return true;
|
|
233
|
-
}
|
|
234
|
-
|
|
233
|
+
}
|
|
235
234
|
|
|
235
|
+
// Helper to find and return a matching flag by name.
|
|
236
236
|
function findFlag(name, parsed, map) {
|
|
237
237
|
if (name.startsWith('--')) name = name.substr(2);
|
|
238
238
|
let {
|
|
@@ -240,61 +240,62 @@ function findFlag(name, parsed, map) {
|
|
|
240
240
|
} = parsed.command.definition;
|
|
241
241
|
let match = flags.find(f => f.name === name);
|
|
242
242
|
return map ? map(match) : match;
|
|
243
|
-
}
|
|
244
|
-
|
|
243
|
+
}
|
|
245
244
|
|
|
245
|
+
// Throws an error if the provided flag is inclusive of any missing flags.
|
|
246
246
|
function validateInclusiveFlag(flag, parsed) {
|
|
247
247
|
if (!flag.inclusive) return;
|
|
248
|
-
let invalid = flag.inclusive.reduce((invalid, name) => findFlag(name, parsed, flag =>
|
|
248
|
+
let invalid = flag.inclusive.reduce((invalid, name) => findFlag(name, parsed, flag =>
|
|
249
|
+
// invalid options are options that have _not_ been provided
|
|
249
250
|
!parsed.operators.has(flag) ? invalid.concat(flag) : invalid), []);
|
|
250
|
-
|
|
251
251
|
if (invalid.length) {
|
|
252
252
|
// show full flag usage for invalid options
|
|
253
253
|
invalid = [flag, ...invalid].map(f => flagUsage(f)).join("', '");
|
|
254
254
|
parseError(`Options must be used together: '${invalid}'`);
|
|
255
255
|
}
|
|
256
|
-
}
|
|
257
|
-
|
|
256
|
+
}
|
|
258
257
|
|
|
258
|
+
// Throws an error if the provided flag is exclusive of other provided flags.
|
|
259
259
|
function validateExclusiveFlag(flag, parsed) {
|
|
260
260
|
if (!flag.exclusive) return;
|
|
261
|
-
let invalid = flag.exclusive.reduce((invalid, name) => findFlag(name, parsed, flag =>
|
|
261
|
+
let invalid = flag.exclusive.reduce((invalid, name) => findFlag(name, parsed, flag =>
|
|
262
|
+
// invalid options are options that _have_ been provided
|
|
262
263
|
parsed.operators.has(flag) ? invalid.concat(flag) : invalid), []);
|
|
263
|
-
|
|
264
264
|
if (invalid.length) {
|
|
265
265
|
// show full flag usage for invalid options
|
|
266
266
|
invalid = [flag, ...invalid].map(f => flagUsage(f)).join("', '");
|
|
267
267
|
parseError(`Options cannot be used together: '${invalid}'`);
|
|
268
268
|
}
|
|
269
|
-
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Returns the attribute name of an option, which may be contingent on it's parsed value. If an
|
|
270
272
|
// option is deprecated and is recommended to be replaced with flag, the attribute name will be
|
|
271
273
|
// mapped to the replacement flag's attribute name. If a deprecated option is found in the provided
|
|
272
274
|
// operators, a deprecation warning will be logged with any recommendation message.
|
|
273
|
-
|
|
274
|
-
|
|
275
275
|
function attributeName(option, parsed) {
|
|
276
276
|
let {
|
|
277
277
|
attribute,
|
|
278
278
|
deprecated
|
|
279
279
|
} = option;
|
|
280
|
-
let value = parsed.operators.get(option) ?? option.default;
|
|
281
|
-
|
|
282
|
-
let name = typeof attribute === 'function' ? attribute(value) : attribute;
|
|
283
|
-
|
|
284
|
-
name || (name = option.name ? camelcase(option.name) : option.short);
|
|
280
|
+
let value = parsed.operators.get(option) ?? option.default;
|
|
281
|
+
// the option's attribute name might be contingent on it's value
|
|
282
|
+
let name = typeof attribute === 'function' ? attribute(value) : attribute;
|
|
283
|
+
// default the attribute name to the camelcase name or short character
|
|
284
|
+
name || (name = option.name ? camelcase(option.name) : option.short);
|
|
285
285
|
|
|
286
|
+
// deprecated options can be mapped to other options
|
|
286
287
|
if (deprecated) {
|
|
287
288
|
var _recommend;
|
|
289
|
+
let [until, recommend] = Array.isArray(deprecated) ? deprecated : [typeof deprecated === 'string' ? deprecated : ''];
|
|
288
290
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
+
// recommended replacement flag
|
|
291
292
|
if ((_recommend = recommend) !== null && _recommend !== void 0 && _recommend.startsWith('--')) {
|
|
292
293
|
let opt = findFlag(recommend, parsed);
|
|
293
294
|
recommend = opt ? `Use '${flagUsage(opt)}' instead.` : '';
|
|
294
295
|
name = opt ? attributeName(opt, parsed) : name;
|
|
295
|
-
}
|
|
296
|
-
|
|
296
|
+
}
|
|
297
297
|
|
|
298
|
+
// operator was provided, log deprecation warning
|
|
298
299
|
if (parsed.operators.has(option)) {
|
|
299
300
|
// show better usage message for flags
|
|
300
301
|
let flag = parsed.command.definition.flags.includes(option);
|
|
@@ -302,34 +303,36 @@ function attributeName(option, parsed) {
|
|
|
302
303
|
parsed.log.deprecated((`The '${usage}' ${flag ? 'option' : 'argument'} ` + `will be removed in ${until || 'a future release'}. ` + (recommend || '')).trim());
|
|
303
304
|
}
|
|
304
305
|
}
|
|
305
|
-
|
|
306
306
|
return name;
|
|
307
|
-
}
|
|
308
|
-
// commands from any provided definition commands method.
|
|
309
|
-
|
|
307
|
+
}
|
|
310
308
|
|
|
309
|
+
// Normalizes a command runner into an object of command properties. Optionally derives nested
|
|
310
|
+
// commands from any provided definition commands method.
|
|
311
311
|
async function normalizeCommand(command, properties) {
|
|
312
312
|
var _ref;
|
|
313
|
-
|
|
314
313
|
// shallow copy command definition
|
|
315
|
-
let {
|
|
316
|
-
|
|
314
|
+
let {
|
|
315
|
+
...definition
|
|
316
|
+
} = command.definition;
|
|
317
317
|
|
|
318
|
+
// evaluate commands if needed
|
|
318
319
|
if (typeof definition.commands === 'function') {
|
|
319
320
|
definition.commands = await definition.commands();
|
|
320
|
-
}
|
|
321
|
-
|
|
321
|
+
}
|
|
322
322
|
|
|
323
|
-
|
|
323
|
+
// create a shallow copy with additional properties
|
|
324
|
+
let normalized = {
|
|
325
|
+
...command,
|
|
324
326
|
...properties,
|
|
325
327
|
definition
|
|
326
|
-
};
|
|
328
|
+
};
|
|
327
329
|
|
|
330
|
+
// inherit parent package information by default
|
|
328
331
|
normalized.packageInformation || (normalized.packageInformation = (_ref = (properties === null || properties === void 0 ? void 0 : properties.parent) || command.parent) === null || _ref === void 0 ? void 0 : _ref.packageInformation);
|
|
329
332
|
return normalized;
|
|
330
|
-
}
|
|
331
|
-
|
|
333
|
+
}
|
|
332
334
|
|
|
335
|
+
// Parses and validates command-line arguments according to a command definition.
|
|
333
336
|
export async function parse(command, argv) {
|
|
334
337
|
// initial input and output
|
|
335
338
|
let input = argv.slice().filter(a => a != null);
|
|
@@ -340,26 +343,26 @@ export async function parse(command, argv) {
|
|
|
340
343
|
flags: {},
|
|
341
344
|
args: {},
|
|
342
345
|
argv: []
|
|
343
|
-
};
|
|
346
|
+
};
|
|
344
347
|
|
|
348
|
+
// while there is input left, parse it
|
|
345
349
|
while (input.length) {
|
|
346
350
|
// parse and mutate input and output, looping if an option is found
|
|
347
351
|
if (await maybeParseCommand(input, parsed)) continue;
|
|
348
352
|
if (await maybeParseFlag(input, parsed)) continue;
|
|
349
353
|
if (await maybeParseArg(input, parsed)) continue;
|
|
350
354
|
await maybeParseUnknown(input, parsed);
|
|
351
|
-
}
|
|
352
|
-
|
|
355
|
+
}
|
|
353
356
|
|
|
357
|
+
// help or version requested, skip remaining parse logic
|
|
354
358
|
if (parsed.help || parsed.version) {
|
|
355
359
|
return parsed;
|
|
356
|
-
}
|
|
357
|
-
|
|
360
|
+
}
|
|
358
361
|
|
|
362
|
+
// validate and map defined flags
|
|
359
363
|
for (let flag of parsed.command.definition.flags) {
|
|
360
364
|
// collect last-minute env info
|
|
361
365
|
await maybeParseEnv(flag, parsed);
|
|
362
|
-
|
|
363
366
|
if (parsed.operators.has(flag)) {
|
|
364
367
|
// validate before setting the parsed attribute
|
|
365
368
|
validateInclusiveFlag(flag, parsed);
|
|
@@ -371,20 +374,19 @@ export async function parse(command, argv) {
|
|
|
371
374
|
// a default value needs to be set, but not parsed
|
|
372
375
|
parsed.flags[attributeName(flag, parsed)] = flag.default;
|
|
373
376
|
}
|
|
374
|
-
}
|
|
375
|
-
|
|
377
|
+
}
|
|
376
378
|
|
|
379
|
+
// validate and map defined args
|
|
377
380
|
for (let arg of parsed.command.definition.args ?? []) {
|
|
378
381
|
// collect last-minute env info
|
|
379
382
|
await maybeParseEnv(arg, parsed);
|
|
380
|
-
|
|
381
383
|
if (parsed.operators.has(arg)) {
|
|
382
384
|
var _arg$deprecated, _arg$deprecated$;
|
|
383
|
-
|
|
384
385
|
// validate before setting the parsed attribute
|
|
385
386
|
let value = parsed.operators.get(arg);
|
|
386
|
-
if (arg.validate) await arg.validate(value, parsed);
|
|
387
|
+
if (arg.validate) await arg.validate(value, parsed);
|
|
387
388
|
|
|
389
|
+
// a deprecated arg might be mapped to a flag
|
|
388
390
|
if ((_arg$deprecated = arg.deprecated) !== null && _arg$deprecated !== void 0 && (_arg$deprecated$ = _arg$deprecated[1]) !== null && _arg$deprecated$ !== void 0 && _arg$deprecated$.startsWith('--') && findFlag(arg.deprecated[1], parsed)) {
|
|
389
391
|
parsed.flags[attributeName(arg, parsed)] = value;
|
|
390
392
|
} else {
|
|
@@ -398,7 +400,6 @@ export async function parse(command, argv) {
|
|
|
398
400
|
parsed.args[attributeName(arg, parsed)] = arg.default;
|
|
399
401
|
}
|
|
400
402
|
}
|
|
401
|
-
|
|
402
403
|
return parsed;
|
|
403
404
|
}
|
|
404
405
|
export default parse;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@percy/cli-command",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
"test:coverage": "yarn test --coverage"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@percy/config": "1.
|
|
39
|
-
"@percy/core": "1.
|
|
40
|
-
"@percy/logger": "1.
|
|
38
|
+
"@percy/config": "1.14.0",
|
|
39
|
+
"@percy/core": "1.14.0",
|
|
40
|
+
"@percy/logger": "1.14.0"
|
|
41
41
|
},
|
|
42
|
-
"gitHead": "
|
|
42
|
+
"gitHead": "fd72688e449d6dd3eafd346fc07879cb3bb01a4e"
|
|
43
43
|
}
|