@percy/cli-command 1.0.0-beta.71 → 1.0.0-beta.72

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/parse.js ADDED
@@ -0,0 +1,426 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = exports.ParseError = void 0;
7
+ exports.parse = parse;
8
+
9
+ var _logger = _interopRequireDefault(require("@percy/logger"));
10
+
11
+ var _normalize = require("@percy/config/dist/normalize");
12
+
13
+ var _help = require("./help");
14
+
15
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
16
+
17
+ function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
18
+
19
+ // Make it possible to identify parse errors.
20
+ class ParseError extends Error {
21
+ constructor(...args) {
22
+ super(...args);
23
+
24
+ _defineProperty(this, "name", this.constructor.name);
25
+ }
26
+
27
+ } // Helper that makes it possible to throw parse errors without throw or new keywords.
28
+
29
+
30
+ exports.ParseError = ParseError;
31
+
32
+ function parseError(msg) {
33
+ throw new ParseError(msg);
34
+ } // Helper to identify a potential command-line option as a flag.
35
+
36
+
37
+ function isFlag(option) {
38
+ // not a flag if it doesn't start with a dash
39
+ if (!option.startsWith('-')) return false; // safe to say it's a flag without a space
40
+
41
+ if (!option.includes(' ')) return true; // containing a space with no operator is not a proper flag
42
+
43
+ if (!option.includes('=')) return false; // as long as the operator precedes the space, it is a flag
44
+
45
+ return option.indexOf('=') < option.indexOf(' ');
46
+ } // Parses input to identify a command. Consumes input and adds to parsed output as needed, returning
47
+ // true if any defined command was found. A command is only identified via leading unparsed input
48
+ // options. If a command is found, parsed output is updated accordingly and the current input option
49
+ // is removed from further processing. An implicit help command is also supported.
50
+
51
+
52
+ async function maybeParseCommand(input, parsed) {
53
+ var _input$0$split, _input$;
54
+
55
+ // a flag, no defined commands, or already parsed other options
56
+ if (isFlag(input[0]) || !parsed.command.definition.commands || parsed.operators.size || parsed.argv.length) {
57
+ return;
58
+ } // implicit help
59
+
60
+
61
+ if (input[0] === 'help') {
62
+ parsed.help = true;
63
+ input.shift();
64
+ } // nested commands can be a single argument with command names separated by colons (:)
65
+
66
+
67
+ let names = (_input$0$split = (_input$ = input[0]) === null || _input$ === void 0 ? void 0 : _input$.split(':')) !== null && _input$0$split !== void 0 ? _input$0$split : [];
68
+ let command;
69
+
70
+ for (let name of names) {
71
+ var _parent$definition$co;
72
+
73
+ let parent = command || parsed.command; // find any nested command by name
74
+
75
+ command = (_parent$definition$co = parent.definition.commands) === null || _parent$definition$co === void 0 ? void 0 : _parent$definition$co.find(cmd => cmd.name === name);
76
+ if (!command) break; // maintain nested names and parent relationships
77
+
78
+ name = parent.parent ? `${parent.name}:${command.name}` : command.name;
79
+ command = await normalizeCommand(command, {
80
+ name,
81
+ parent
82
+ });
83
+ }
84
+
85
+ if (command) {
86
+ // update parsed properties and discard current input
87
+ parsed.log = (0, _logger.default)(`cli:${command.name}`);
88
+ parsed.command = command;
89
+ input.shift();
90
+ } // help was requested, drain remaining input
91
+
92
+
93
+ if (parsed.help) {
94
+ input.splice(0, input.length);
95
+ }
96
+
97
+ return parsed.help || !!command;
98
+ } // Parses input to identify a flag option. Consumes input and adds to parsed output as needed,
99
+ // returning true if any defined flag was found. If a found flag is missing a required argument, the
100
+ // next input option will be consumed as that argument. Boolean flags support negation if their
101
+ // default value is true, integer flags are automaticaly parsed, and flags that can be provided
102
+ // multiple times will be concatenated together. Implicit help and version flags are also supported.
103
+
104
+
105
+ async function maybeParseFlag(input, parsed) {
106
+ var _value;
107
+
108
+ if (!isFlag(input[0])) return; // end of options and arguments, consume remaining input
109
+
110
+ if (input[0] === '--') {
111
+ let [, ...argv] = input.splice(0, input.length);
112
+ parsed.argv.push(...argv);
113
+ return true;
114
+ } // a short flag was provided
115
+
116
+
117
+ let short = input[0][1] !== '-' && input[0][1]; // get any value set with `=` and remove any `=` from short values
118
+
119
+ let [name, value] = short ? ['', input[0].substr(2) || null] : input[0].split('=');
120
+ value = short && (_value = value) !== null && _value !== void 0 && _value.startsWith('=') ? value.substr(1) : value; // the flag was negated
121
+
122
+ let negated = name.startsWith('--no-');
123
+ name = name.replace(/^--(no-)?/, ''); // search for the provided short or long flag
124
+
125
+ let flag = parsed.command.definition.flags.find(f => {
126
+ return short ? f.short === short : f.name === name;
127
+ });
128
+
129
+ if (flag) {
130
+ // discard current input
131
+ input.shift();
132
+
133
+ if (!flag.type || flag.type === 'boolean') {
134
+ // short boolean flags can be provided with other short flags
135
+ if (short && value) input.unshift(`-${value}`); // a value was provided to a boolean flag
136
+
137
+ if (!short && value) {
138
+ parseError(`Unexpected option argument for '${(0, _help.flagUsage)(flag)}'`);
139
+ } // a boolean value is based on if it was negated
140
+
141
+
142
+ value = !negated;
143
+ } else if (value == null) {
144
+ // expected a value for a non-boolean flag
145
+ if (!input.length || isFlag(input[0])) {
146
+ parseError(`Missing option argument for '${(0, _help.flagUsage)(flag)}'`);
147
+ } // the value is actually the next input option
148
+
149
+
150
+ value = input.shift();
151
+ } // parse the value according to flag properties
152
+
153
+
154
+ if (flag.type === 'integer') value = parseInt(value, 10);
155
+ if (flag.parse) value = await flag.parse(value);
156
+
157
+ if (flag.multiple) {
158
+ var _parsed$operators$get;
159
+
160
+ value = ((_parsed$operators$get = parsed.operators.get(flag)) !== null && _parsed$operators$get !== void 0 ? _parsed$operators$get : []).concat(value);
161
+ } // add this flag to the set of parsed operators
162
+
163
+
164
+ parsed.operators.set(flag, value);
165
+ return true;
166
+ } // implicit version and help support
167
+
168
+
169
+ if (parsed.command.definition.version && (name === 'version' || short === 'V')) {
170
+ parsed.version = true;
171
+ } else if (name === 'help' || short === 'h') {
172
+ parsed.help = true;
173
+ } // help or version was requested, drain remaining input
174
+
175
+
176
+ if (parsed.help || parsed.version) {
177
+ input.splice(0, input.length);
178
+ return true;
179
+ }
180
+ } // Parses input to identify an argument option. Consumes input and adds to parsed output as needed,
181
+ // returning true if any defined argument was found. Arguments are matched according to their
182
+ // respective index and command-line position.
183
+
184
+
185
+ async function maybeParseArg(input, parsed) {
186
+ var _parsed$__args_index_, _parsed$command$defin;
187
+
188
+ if (isFlag(input[0])) return; // the argument index needs to be tracked since flag options can precede argument options
189
+
190
+ let index = (_parsed$__args_index_ = parsed.__args_index__) !== null && _parsed$__args_index_ !== void 0 ? _parsed$__args_index_ : parsed.__args_index__ = 0; // there are arguments left to parse
191
+
192
+ if (index < ((_parsed$command$defin = parsed.command.definition.args) === null || _parsed$command$defin === void 0 ? void 0 : _parsed$command$defin.length)) {
193
+ parsed.__args_index__ += 1;
194
+ let value = input.shift();
195
+ let arg = parsed.command.definition.args[index];
196
+ if (arg.parse) value = await arg.parse(value); // add this arg to the set of parsed operators
197
+
198
+ parsed.operators.set(arg, value);
199
+ return true;
200
+ }
201
+ } // Parses input to identify unknown options. Consumes input and adds to parsed output as needed,
202
+ // returning true when unknown options are found. By default, unknown options result in an error
203
+ // unless the command is defined to accept loose options. If `loose` is a string, it will be logged
204
+ // as a warning when unknown options are found. When the first unknown option is found, all
205
+ // remaining options are also considered unknown.
206
+
207
+
208
+ async function maybeParseUnknown(input, parsed) {
209
+ let {
210
+ loose
211
+ } = parsed.command.definition;
212
+
213
+ if (!loose) {
214
+ // throw a more descriptive error message
215
+ let reason = isFlag(input[0]) ? 'Unknown option' : 'Unexpected argument';
216
+ parseError(`${reason} '${input[0]}'`);
217
+ } else if (typeof loose === 'string') {
218
+ // warn when loose is a string
219
+ parsed.log.warn(loose);
220
+ } // consume all remaining unknown input options
221
+
222
+
223
+ let unknown = input.splice(0, input.length);
224
+ parsed.argv.push(...unknown);
225
+ return true;
226
+ } // Parses an option's value defined by an environment variable. Adds to parsed output as needed,
227
+ // returning true when the option's value was parsed from the environment. Boolean environment
228
+ // variables will be interpreted as true unless "false" or "0", and integer environment variables
229
+ // will be parsed automatically.
230
+
231
+
232
+ async function maybeParseEnv(option, parsed) {
233
+ // no env option, already provided, or no defined env var
234
+ if (!option.env || parsed.operators.has(option) || !(option.env in process.env)) {
235
+ return;
236
+ } // get the value from the process env
237
+
238
+
239
+ let value = process.env[option.env];
240
+ let flag = parsed.command.definition.flags.includes(option);
241
+
242
+ if (flag && (!option.type || option.type === 'boolean')) {
243
+ // parse booleans as not "false" or not "0"
244
+ value = value !== 'false' && value !== '0';
245
+ } else if (option.type === 'integer') {
246
+ // parse integer option values
247
+ value = parseInt(value, 10);
248
+ } // custom parse and add this option to the set of parsed operators
249
+
250
+
251
+ if (option.parse) value = await option.parse(value);
252
+ parsed.operators.set(option, value);
253
+ return true;
254
+ } // Helper to find and return a matching flag by name.
255
+
256
+
257
+ function findFlag(name, parsed, map) {
258
+ if (name.startsWith('--')) name = name.substr(2);
259
+ let {
260
+ flags
261
+ } = parsed.command.definition;
262
+ let match = flags.find(f => f.name === name);
263
+ return map ? map(match) : match;
264
+ } // Throws an error if the provided flag is inclusive of any missing flags.
265
+
266
+
267
+ function validateInclusiveFlag(flag, parsed) {
268
+ if (!flag.inclusive) return;
269
+ let invalid = flag.inclusive.reduce((invalid, name) => findFlag(name, parsed, flag => // invalid options are options that have _not_ been provided
270
+ !parsed.operators.has(flag) ? invalid.concat(flag) : invalid), []);
271
+
272
+ if (invalid.length) {
273
+ // show full flag usage for invalid options
274
+ invalid = [flag, ...invalid].map(f => (0, _help.flagUsage)(f)).join("', '");
275
+ parseError(`Options must be used together: '${invalid}'`);
276
+ }
277
+ } // Throws an error if the provided flag is exclusive of other provided flags.
278
+
279
+
280
+ function validateExclusiveFlag(flag, parsed) {
281
+ if (!flag.exclusive) return;
282
+ let invalid = flag.exclusive.reduce((invalid, name) => findFlag(name, parsed, flag => // invalid options are options that _have_ been provided
283
+ parsed.operators.has(flag) ? invalid.concat(flag) : invalid), []);
284
+
285
+ if (invalid.length) {
286
+ // show full flag usage for invalid options
287
+ invalid = [flag, ...invalid].map(f => (0, _help.flagUsage)(f)).join("', '");
288
+ parseError(`Options cannot be used together: '${invalid}'`);
289
+ }
290
+ } // Returns the attribute name of an option, which may be contingent on it's parsed value. If an
291
+ // option is deprecated and is recommended to be replaced with flag, the attribute name will be
292
+ // mapped to the replacement flag's attribute name. If a deprecated option is found in the provided
293
+ // operators, a deprecation warning will be logged with any recommendation message.
294
+
295
+
296
+ function attributeName(option, parsed) {
297
+ var _parsed$operators$get2;
298
+
299
+ let {
300
+ attribute,
301
+ deprecated
302
+ } = option;
303
+ let value = (_parsed$operators$get2 = parsed.operators.get(option)) !== null && _parsed$operators$get2 !== void 0 ? _parsed$operators$get2 : option.default; // the option's attribute name might be contingent on it's value
304
+
305
+ let name = typeof attribute === 'function' ? attribute(value) : attribute; // default the attribute name to the camelcase name or short character
306
+
307
+ name || (name = option.name ? (0, _normalize.camelcase)(option.name) : option.short); // deprecated options can be mapped to other options
308
+
309
+ if (deprecated) {
310
+ var _recommend;
311
+
312
+ let [until, recommend] = Array.isArray(deprecated) ? deprecated : [typeof deprecated === 'string' ? deprecated : '']; // recommended replacement flag
313
+
314
+ if ((_recommend = recommend) !== null && _recommend !== void 0 && _recommend.startsWith('--')) {
315
+ let opt = findFlag(recommend, parsed);
316
+ recommend = opt ? `Use '${(0, _help.flagUsage)(opt)}' instead.` : '';
317
+ name = opt ? attributeName(opt, parsed) : name;
318
+ } // operator was provided, log deprecation warning
319
+
320
+
321
+ if (parsed.operators.has(option)) {
322
+ // show better usage message for flags
323
+ let flag = parsed.command.definition.flags.includes(option);
324
+ let usage = flag ? (0, _help.flagUsage)(option) : option.name;
325
+ parsed.log.deprecated((`The '${usage}' ${flag ? 'option' : 'argument'} ` + `will be removed in ${until || 'a future release'}. ` + (recommend || '')).trim());
326
+ }
327
+ }
328
+
329
+ return name;
330
+ } // Normalizes a command runner into an object of command properties. Optionally derives nested
331
+ // commands from any provided definition commands method.
332
+
333
+
334
+ async function normalizeCommand(command, properties) {
335
+ // shallow copy command definition
336
+ let { ...definition
337
+ } = command.definition; // evaluate commands if needed
338
+
339
+ if (typeof definition.commands === 'function') {
340
+ definition.commands = await definition.commands();
341
+ } // return shallow copy with additional properties
342
+
343
+
344
+ return { ...command,
345
+ ...properties,
346
+ definition
347
+ };
348
+ } // Parses and validates command-line arguments according to a command definition.
349
+
350
+
351
+ async function parse(command, argv) {
352
+ // initial input and output
353
+ let input = argv.slice().filter(a => a != null);
354
+ let parsed = {
355
+ command: await normalizeCommand(command),
356
+ operators: new Map(),
357
+ log: (0, _logger.default)('cli'),
358
+ flags: {},
359
+ args: {},
360
+ argv: []
361
+ }; // while there is input left, parse it
362
+
363
+ while (input.length) {
364
+ // parse and mutate input and output, looping if an option is found
365
+ if (await maybeParseCommand(input, parsed)) continue;
366
+ if (await maybeParseFlag(input, parsed)) continue;
367
+ if (await maybeParseArg(input, parsed)) continue;
368
+ await maybeParseUnknown(input, parsed);
369
+ } // help or version requested, skip remaining parse logic
370
+
371
+
372
+ if (parsed.help || parsed.version) {
373
+ return parsed;
374
+ } // validate and map defined flags
375
+
376
+
377
+ for (let flag of parsed.command.definition.flags) {
378
+ // collect last-minute env info
379
+ await maybeParseEnv(flag, parsed);
380
+
381
+ if (parsed.operators.has(flag)) {
382
+ // validate before setting the parsed attribute
383
+ validateInclusiveFlag(flag, parsed);
384
+ validateExclusiveFlag(flag, parsed);
385
+ let value = parsed.operators.get(flag);
386
+ if (flag.validate) await flag.validate(value, parsed);
387
+ parsed.flags[attributeName(flag, parsed)] = value;
388
+ } else if (flag.default != null) {
389
+ // a default value needs to be set, but not parsed
390
+ parsed.flags[attributeName(flag, parsed)] = flag.default;
391
+ }
392
+ } // validate and map defined args
393
+
394
+
395
+ for (let arg of (_parsed$command$defin2 = parsed.command.definition.args) !== null && _parsed$command$defin2 !== void 0 ? _parsed$command$defin2 : []) {
396
+ var _parsed$command$defin2;
397
+
398
+ // collect last-minute env info
399
+ await maybeParseEnv(arg, parsed);
400
+
401
+ if (parsed.operators.has(arg)) {
402
+ var _arg$deprecated, _arg$deprecated$;
403
+
404
+ // validate before setting the parsed attribute
405
+ let value = parsed.operators.get(arg);
406
+ if (arg.validate) await arg.validate(value, parsed); // a deprecated arg might be mapped to a flag
407
+
408
+ 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)) {
409
+ parsed.flags[attributeName(arg, parsed)] = value;
410
+ } else {
411
+ parsed.args[attributeName(arg, parsed)] = value;
412
+ }
413
+ } else if (arg.required) {
414
+ // a required argument was not provided
415
+ parseError(`Missing required argument '${arg.name}'`);
416
+ } else if (arg.default != null) {
417
+ // a default value needs to be set, but not parsed
418
+ parsed.args[attributeName(arg, parsed)] = arg.default;
419
+ }
420
+ }
421
+
422
+ return parsed;
423
+ }
424
+
425
+ var _default = parse;
426
+ exports.default = _default;
package/package.json CHANGED
@@ -1,12 +1,21 @@
1
1
  {
2
2
  "name": "@percy/cli-command",
3
- "version": "1.0.0-beta.71",
3
+ "version": "1.0.0-beta.72",
4
4
  "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/percy/cli",
8
+ "directory": "packages/cli-command"
9
+ },
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
5
13
  "main": "dist/index.js",
6
- "types": "types/index.d.ts",
14
+ "bin": {
15
+ "percy-cli-readme": "bin/readme"
16
+ },
7
17
  "files": [
8
- "dist",
9
- "types/index.d.ts"
18
+ "dist"
10
19
  ],
11
20
  "engines": {
12
21
  "node": ">=12"
@@ -15,23 +24,12 @@
15
24
  "build": "node ../../scripts/build",
16
25
  "lint": "eslint --ignore-path ../../.gitignore .",
17
26
  "test": "node ../../scripts/test",
18
- "test:coverage": "yarn test --coverage",
19
- "test:types": "tsd"
20
- },
21
- "publishConfig": {
22
- "access": "public"
27
+ "test:coverage": "yarn test --coverage"
23
28
  },
24
29
  "dependencies": {
25
- "@oclif/command": "^1.8.0",
26
- "@oclif/config": "^1.17.0",
27
- "@oclif/plugin-help": "^3.2.0",
28
- "@percy/config": "1.0.0-beta.71",
29
- "@percy/logger": "1.0.0-beta.71"
30
- },
31
- "repository": {
32
- "type": "git",
33
- "url": "https://github.com/percy/cli",
34
- "directory": "packages/cli-command"
30
+ "@percy/config": "1.0.0-beta.72",
31
+ "@percy/core": "1.0.0-beta.72",
32
+ "@percy/logger": "1.0.0-beta.72"
35
33
  },
36
- "gitHead": "364d1df717fb19a26ccb024458df6e78a9c11f99"
34
+ "gitHead": "6219287e18a0cacb609d0c2696a5785abc9009b9"
37
35
  }
package/types/index.d.ts DELETED
@@ -1,45 +0,0 @@
1
- // Minimum TypeScript Version: 3.8
2
- import Command from '@oclif/command';
3
- import * as Parser from '@oclif/parser';
4
-
5
- export namespace flags {
6
- interface PercyFlagBase { percyrc?: string }
7
- interface PercyOptionFlag<T> extends PercyFlagBase, Parser.flags.IOptionFlag<T> {}
8
- interface PercyBooleanFlag<T> extends PercyFlagBase, Parser.flags.IBooleanFlag<T> {}
9
- interface PercyDefinition<T> extends Parser.flags.Definition<T> {
10
- (options: { multiple: true } & Partial<PercyOptionFlag<T[]>>): PercyOptionFlag<T[]>;
11
- (options: ({ required: true } | { default: Parser.flags.Default<T> }) & Partial<PercyOptionFlag<T>>): PercyOptionFlag<T>;
12
- (options?: Partial<PercyOptionFlag<T>>): PercyOptionFlag<T | undefined>;
13
- }
14
-
15
- type PercyParseOptions<T> = { parse: PercyOptionFlag<T>['parse'] } & Partial<PercyOptionFlag<T>>;
16
- function build<T>(defaults: PercyParseOptions<T>): PercyDefinition<T>;
17
- function build(defaults: Partial<PercyOptionFlag<string>>): PercyDefinition<string>;
18
- function option<T>(options: PercyParseOptions<T>): PercyOptionFlag<T | undefined>;
19
- function boolean<T = boolean>(options?: Partial<PercyBooleanFlag<T>>): PercyBooleanFlag<T>;
20
- const integer: PercyDefinition<number>;
21
- const string: PercyDefinition<string>;
22
-
23
- const logging: {
24
- verbose: PercyOptionFlag<boolean>,
25
- quiet: PercyOptionFlag<boolean>,
26
- silent: PercyOptionFlag<boolean>
27
- };
28
-
29
- const discovery: {
30
- ['allowed-hostnames']: PercyOptionFlag<string[]>,
31
- ['network-idle-timeout']: PercyOptionFlag<number>,
32
- ['disable-cache']: PercyOptionFlag<boolean>
33
- };
34
-
35
- const config: {
36
- config: PercyOptionFlag<string>
37
- };
38
- }
39
-
40
- export default abstract class PercyCommand extends Command {
41
- isPercyEnabled(): boolean
42
- percyrc(): { [x: string]: any }
43
- flags: { [x: string]: any }
44
- args: { [x: string]: any }
45
- }