@percy/cli-command 1.0.0-beta.8 → 1.0.1

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/flags.js CHANGED
@@ -1,58 +1,85 @@
1
- "use strict";
1
+ import logger from '@percy/logger'; // Global CLI flags
2
2
 
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
-
8
- var _command = require("@oclif/command");
9
-
10
- // Common logging flags exclusive of each other.
11
- const logging = {
12
- verbose: _command.flags.boolean({
13
- char: 'v',
14
- description: 'log everything',
15
- exclusive: ['quiet', 'silent']
16
- }),
17
- quiet: _command.flags.boolean({
18
- char: 'q',
19
- description: 'log errors only',
20
- exclusive: ['verbose', 'silent']
21
- }),
22
- silent: _command.flags.boolean({
23
- description: 'log nothing',
24
- exclusive: ['verbose', 'quiet']
25
- })
26
- }; // Common asset discovery flags mapped to config options.
27
-
28
- const discovery = {
29
- 'allowed-hostname': _command.flags.string({
30
- char: 'h',
31
- description: 'allowed hostnames',
32
- multiple: true,
33
- percyrc: 'discovery.allowedHostnames'
34
- }),
35
- 'network-idle-timeout': _command.flags.integer({
36
- char: 't',
37
- description: 'asset discovery idle timeout',
38
- percyrc: 'discovery.networkIdleTimeout'
39
- }),
40
- 'disable-asset-cache': _command.flags.boolean({
41
- description: 'disable asset discovery caches',
42
- percyrc: 'discovery.disableAssetCache'
43
- })
44
- }; // Common flag for loading config files.
45
-
46
- const config = {
47
- config: _command.flags.string({
48
- char: 'c',
49
- description: 'configuration file path'
50
- })
51
- }; // Export a single object imported as `flags`
3
+ export const verbose = {
4
+ name: 'verbose',
5
+ description: 'Log everything',
6
+ exclusive: ['quiet', 'silent'],
7
+ parse: () => !!logger.loglevel('debug'),
8
+ group: 'Global',
9
+ short: 'v'
10
+ };
11
+ export const quiet = {
12
+ name: 'quiet',
13
+ description: 'Log errors only',
14
+ exclusive: ['verbose', 'silent'],
15
+ parse: () => !!logger.loglevel('warn'),
16
+ group: 'Global',
17
+ short: 'q'
18
+ };
19
+ export const silent = {
20
+ name: 'silent',
21
+ description: 'Log nothing',
22
+ exclusive: ['verbose', 'quiet'],
23
+ parse: () => !!logger.loglevel('silent'),
24
+ group: 'Global',
25
+ short: 's'
26
+ }; // Common percy and asset discovery flags
52
27
 
53
- var _default = { ..._command.flags,
54
- logging,
55
- discovery,
56
- config
28
+ export const config = {
29
+ name: 'config',
30
+ description: 'Config file path',
31
+ percyrc: 'config',
32
+ type: 'file',
33
+ group: 'Percy',
34
+ short: 'c'
57
35
  };
58
- exports.default = _default;
36
+ export const dryRun = {
37
+ name: 'dry-run',
38
+ description: 'Print snapshot names only',
39
+ percyrc: 'dryRun',
40
+ group: 'Percy',
41
+ short: 'd'
42
+ };
43
+ export const allowedHostnames = {
44
+ name: 'allowed-hostname',
45
+ description: 'Allowed hostnames to capture in asset discovery',
46
+ percyrc: 'discovery.allowedHostnames',
47
+ type: 'hostname',
48
+ multiple: true,
49
+ group: 'Percy',
50
+ short: 'h'
51
+ };
52
+ export const disallowedHostnames = {
53
+ name: 'disallowed-hostname',
54
+ description: 'Disallowed hostnames to abort in asset discovery',
55
+ percyrc: 'discovery.disallowedHostnames',
56
+ type: 'hostname',
57
+ multiple: true,
58
+ group: 'Percy'
59
+ };
60
+ export const networkIdleTimeout = {
61
+ name: 'network-idle-timeout',
62
+ description: 'Asset discovery network idle timeout',
63
+ percyrc: 'discovery.networkIdleTimeout',
64
+ type: 'ms',
65
+ parse: Number,
66
+ group: 'Percy',
67
+ short: 't'
68
+ };
69
+ export const disableCache = {
70
+ name: 'disable-cache',
71
+ description: 'Disable asset discovery caches',
72
+ percyrc: 'discovery.disableCache',
73
+ group: 'Percy'
74
+ };
75
+ export const debug = {
76
+ name: 'debug',
77
+ description: 'Debug asset discovery and do not upload snapshots',
78
+ parse: () => !!logger.loglevel('debug'),
79
+ percyrc: 'skipUploads',
80
+ group: 'Percy'
81
+ }; // Group constants
82
+
83
+ export const GLOBAL = [verbose, quiet, silent];
84
+ export const PERCY = [config, dryRun];
85
+ export const DISCOVERY = [allowedHostnames, disallowedHostnames, networkIdleTimeout, disableCache, debug];
package/dist/help.js ADDED
@@ -0,0 +1,243 @@
1
+ import logger from '@percy/logger'; // Helper to return an argument's description including it's default value.
2
+
3
+ function argumentDescription(arg) {
4
+ let desc = arg.description || '';
5
+
6
+ if (arg.default != null) {
7
+ desc += ` (default: ${JSON.stringify(arg.default)})`;
8
+ }
9
+
10
+ return desc.trim();
11
+ } // Helper to return an array of visible argument usage-description pairs.
12
+
13
+
14
+ async function visibleArguments(command) {
15
+ let args = command.definition.args ?? []; // not hidden and not deprecated
16
+
17
+ let visible = args.reduce((visible, arg) => {
18
+ if (arg.hidden || arg.deprecated) return visible;
19
+ visible.push([arg.name, argumentDescription(arg)]);
20
+ return visible;
21
+ }, []); // do not return any arguments if none have descriptions
22
+
23
+ if (!visible.some(([, desc]) => !!desc)) return [];
24
+ return visible;
25
+ } // Helper to return a command's usage string.
26
+
27
+
28
+ function commandUsage(command) {
29
+ let {
30
+ name,
31
+ definition
32
+ } = command;
33
+ let {
34
+ usage,
35
+ args,
36
+ commands
37
+ } = definition;
38
+
39
+ if (!command.callback && commands !== null && commands !== void 0 && commands.length) {
40
+ // if there are subcommands but no callback show subcommand usage
41
+ usage || (usage = '<command>');
42
+ } else {
43
+ var _args, _args2;
44
+
45
+ // show options followed by arguments
46
+ 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
+ usage || (usage = `[options] ${((_args2 = args) === null || _args2 === void 0 ? void 0 : _args2.join(' ')) || ''}`);
48
+ }
49
+
50
+ return `${name} ${usage}`.trim();
51
+ } // Helper to return an array of visible command usage-description pairs.
52
+
53
+
54
+ async function visibleCommands(command, help) {
55
+ let commands = command.definition.commands ?? [];
56
+ if (typeof commands === 'function') commands = await commands();
57
+ let visible = [];
58
+
59
+ for (let cmd of commands) {
60
+ // create parent references and format subcommand names
61
+ let name = command.parent ? `${command.name}:${cmd.name}` : cmd.name;
62
+ cmd = { ...cmd,
63
+ name,
64
+ parent: command
65
+ }; // commands without callbacks are not visible themselves
66
+
67
+ if (cmd.callback) {
68
+ let {
69
+ description
70
+ } = cmd.definition;
71
+ visible.push([commandUsage(cmd), description]);
72
+ } // include visible subcommands without their implicit help
73
+
74
+
75
+ if (cmd.definition.commands) {
76
+ visible.push(...(await visibleCommands(cmd, false)));
77
+ }
78
+ } // include implicit help if there are visible commands
79
+
80
+
81
+ if (help !== false && visible.length) {
82
+ visible.push(['help [command]', 'Display command help']);
83
+ }
84
+
85
+ return visible;
86
+ } // Helper to return an flags's description including it's default value.
87
+
88
+
89
+ function flagDescription(flag) {
90
+ let desc = flag.description || ''; // a boolean that defaults to false will be displayed with a --no prefix
91
+
92
+ if (flag.type && flag.type !== 'boolean' && flag.default != null) {
93
+ desc += ` (default: ${JSON.stringify(flag.default)})`;
94
+ }
95
+
96
+ return desc.trim();
97
+ } // Helper to return a flag's usage string.
98
+
99
+
100
+ export function flagUsage(flag) {
101
+ let {
102
+ short,
103
+ usage,
104
+ type
105
+ } = flag;
106
+ let term = flag.name ? `--${flag.name}` : '';
107
+
108
+ if (type && type !== 'boolean') {
109
+ // if there is a default it is not required
110
+ usage || (usage = flag.default != null ? `[${type}]` : `<${type}>`);
111
+ } else if (flag.default === true) {
112
+ // negated booleans have a prefix
113
+ term && (term = `--no-${flag.name}`);
114
+ } // add short flag usage
115
+
116
+
117
+ if (short) term = `-${short}${term ? `, ${term}` : ''}`;
118
+ return `${term} ${usage || ''}`.trim();
119
+ } // Helper to return an array of visible flag usage-description pairs. A third entry is also included
120
+ // with the returned pair representing any group the flag should belong to.
121
+
122
+ async function visibleFlags(command) {
123
+ let flags = command.definition.flags; // not hidden and not deprecated
124
+
125
+ let visible = flags.reduce((visible, flag) => {
126
+ if (flag.hidden || flag.deprecated) return visible;
127
+ visible.push([flagUsage(flag), flagDescription(flag), flag.group]);
128
+ return visible;
129
+ }, []); // maybe hide the implicit help long or short flag (or both)
130
+
131
+ let showLongHelpFlag = !flags.some(f => f.name === 'help');
132
+ let showShortHelpFlag = !flags.some(f => f.short === 'h');
133
+
134
+ if (showLongHelpFlag || showShortHelpFlag) {
135
+ let help = {
136
+ name: 'help',
137
+ short: 'h'
138
+ };
139
+ if (!showLongHelpFlag) delete help.name;
140
+ if (!showShortHelpFlag) delete help.short;
141
+ visible.push([flagUsage(help), 'Display command help', 'Global']);
142
+ }
143
+
144
+ if (command.definition.version) {
145
+ // maybe hide the implicit version long or short flag (or both)
146
+ let showLongVerFlag = !flags.some(f => f.name === 'version');
147
+ let showShortVerFlag = !flags.some(f => f.short === 'V');
148
+
149
+ if (showLongVerFlag || showShortVerFlag) {
150
+ let ver = {
151
+ name: 'version',
152
+ short: 'V'
153
+ };
154
+ if (!showLongVerFlag) delete ver.name;
155
+ if (!showShortVerFlag) delete ver.short;
156
+ visible.push([flagUsage(ver), 'Display version', 'Global']);
157
+ }
158
+ }
159
+
160
+ return visible;
161
+ } // Helper to wrap a string at a length by inserting a newline between every segment.
162
+
163
+
164
+ function wrapString(str, len) {
165
+ let regex = new RegExp(`.{1,${len - 1}}(\\s|$)|\\S+?(\\s|$)`, 'g');
166
+ return str.replace(regex, s => s.replace(/\s*\n?$/, str.endsWith(s) ? '' : '\n'));
167
+ } // Helper to format an option usage-description pair into a single string where the description
168
+ // wraps within the available space beside the usage string.
169
+
170
+
171
+ function formatOptionHelp(option, usageWidth, maxWidth) {
172
+ if (!option[1]) return option[0]; // calculate the available description width, at minimum 40
173
+
174
+ let descWidth = Math.max(maxWidth - usageWidth, 40); // pad usage up to the description and indent the description
175
+
176
+ return option[0].padEnd(usageWidth) + wrapString(option[1], descWidth).replace(/\n+/gm, `$&${' '.repeat(usageWidth)}`);
177
+ } // Returns a formatted string displaying help output for the specified command. The help output
178
+ // total width is determined by the logger's stdout TTY columns. Help output includes the command
179
+ // description, usage, arguments, subcommand usage and descriptions, flag and flag group usage and
180
+ // descriptions, and any command examples.
181
+
182
+
183
+ export async function formatHelp(command) {
184
+ var _examples, _examples2;
185
+
186
+ let {
187
+ name,
188
+ parent: top,
189
+ definition
190
+ } = command;
191
+ let {
192
+ description,
193
+ examples
194
+ } = definition;
195
+ let output = []; // collect visible items first to determine max usage width
196
+
197
+ let args = await visibleArguments(command);
198
+ let commands = await visibleCommands(command);
199
+ let flags = await visibleFlags(command); // maximum help output total width
200
+
201
+ let maxWidth = logger.stdout.columns || 100; // calculate the longest usage width
202
+
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)); // format lists of options and add appropriate indentation
204
+
205
+ let formatList = items => items.map(item => Array.isArray(item) ? // indent items and separate columns by 2 spaces each
206
+ formatOptionHelp(item, usageWidth + 2, maxWidth - 2) : item).join('\n').replace(/^(?!$)/gm, ' '.repeat(2)); // format the command name to include the topmost command name
207
+
208
+
209
+ while ((_top = top) !== null && _top !== void 0 && _top.parent) {
210
+ var _top;
211
+
212
+ top = top.parent;
213
+ }
214
+
215
+ if (top) name = `${top.name} ${name}`;
216
+ command = { ...command,
217
+ name
218
+ }; // include command description and own usage
219
+
220
+ if (description) output.push(wrapString(description, maxWidth), '');
221
+ output.push('Usage:', formatList([`$ ${commandUsage(command)}`]), ''); // include command arguments
222
+
223
+ if (args.length) output.push('Arguments:', formatList(args), ''); // include command subcommands
224
+
225
+ let commandGroupName = command.parent ? 'Subcommands:' : 'Commands:';
226
+ if (commands.length) output.push(commandGroupName, formatList(commands), ''); // separate flags into groups
227
+
228
+ let groups = flags.reduce((all, option) => {
229
+ let group = option[2] ? `${option[2]} options` : 'Options';
230
+ return all.set(group, [...(all.get(group) || []), option]);
231
+ }, new Map([['Options', []]])); // include each group of command flags
232
+
233
+ for (let [group, flags] of groups.entries()) {
234
+ if (flags.length) output.push(`${group}:`, formatList(flags), '');
235
+ } // include command examples and replace '$0' with the command name
236
+
237
+
238
+ examples = (_examples = examples) === null || _examples === void 0 ? void 0 : _examples.map(ex => ex.replace('$0', `$ ${name}`));
239
+ if ((_examples2 = examples) !== null && _examples2 !== void 0 && _examples2.length) output.push('Examples:', formatList(examples), ''); // join each line of output
240
+
241
+ return output.join('\n');
242
+ }
243
+ export default formatHelp;
package/dist/index.js CHANGED
@@ -1,23 +1,5 @@
1
- "use strict";
1
+ export { default, command } from './command.js';
2
+ export { legacyCommand, legacyFlags as flags } from './legacy.js'; // export common packages to avoid dependency resolution issues
2
3
 
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- Object.defineProperty(exports, "default", {
7
- enumerable: true,
8
- get: function () {
9
- return _command.default;
10
- }
11
- });
12
- Object.defineProperty(exports, "flags", {
13
- enumerable: true,
14
- get: function () {
15
- return _flags.default;
16
- }
17
- });
18
-
19
- var _command = _interopRequireDefault(require("./command"));
20
-
21
- var _flags = _interopRequireDefault(require("./flags"));
22
-
23
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
4
+ export { default as PercyConfig } from '@percy/config';
5
+ export { default as logger } from '@percy/logger';
package/dist/legacy.js ADDED
@@ -0,0 +1,161 @@
1
+ import { merge } from '@percy/config/utils';
2
+ import { command } from './command.js'; // Legacy flags for older commands that inadvertently import a newer @percy/cli-command
3
+
4
+ export const legacyFlags = {
5
+ config: {},
6
+ logging: {},
7
+ discovery: {
8
+ __percy__: {}
9
+ },
10
+ string: def => ({
11
+ type: 'string',
12
+ ...def
13
+ }),
14
+ boolean: def => ({
15
+ type: 'boolean',
16
+ ...def
17
+ }),
18
+ integer: def => ({
19
+ type: 'integer',
20
+ ...def
21
+ })
22
+ }; // Maps a legacy arg that might have alternate properties
23
+
24
+ function legacyArg(legacy) {
25
+ let {
26
+ name,
27
+ ...def
28
+ } = legacy; // do not camelcase legacy attribute names
29
+
30
+ def.attribute = name; // add name back to mapped options
31
+
32
+ return {
33
+ name,
34
+ ...def
35
+ };
36
+ } // Maps a legacy flag that might have alternate properties
37
+
38
+
39
+ function legacyFlag(name, legacy) {
40
+ let {
41
+ char: short,
42
+ deprecated,
43
+ ...def
44
+ } = legacy; // generic legacy type looks odd in help output
45
+
46
+ if (def.type === 'option') def.type = 'value'; // do not camelcase legacy attribute names
47
+
48
+ def.attribute = name; // transform legacy deprecated syntax
49
+
50
+ if (deprecated) {
51
+ let {
52
+ until,
53
+ map,
54
+ alt
55
+ } = deprecated !== true ? deprecated : {};
56
+ def.deprecated = [until, map ? `--${map}` : alt];
57
+ } // add name and mapped short option
58
+
59
+
60
+ return {
61
+ name,
62
+ short,
63
+ ...def
64
+ };
65
+ } // Maps a legacy class-based command to the new functional syntax
66
+
67
+
68
+ export function legacyCommand(name, constructor) {
69
+ var _constructor$args, _constructor$flags;
70
+
71
+ let runner = command(name, {
72
+ description: constructor.description,
73
+ examples: constructor.examples,
74
+ // map legacy args and flags
75
+ args: (_constructor$args = constructor.args) === null || _constructor$args === void 0 ? void 0 : _constructor$args.map(legacyArg),
76
+ flags: Object.entries(constructor.flags ?? []).map(([name, def]) => legacyFlag(name, def)).filter(({
77
+ name
78
+ }) => name !== '__percy__'),
79
+ percy: ((_constructor$flags = constructor.flags) === null || _constructor$flags === void 0 ? void 0 : _constructor$flags.__percy__) ?? true,
80
+ loose: constructor.strict === false,
81
+ legacy: true
82
+ }, async function* (ctx) {
83
+ let instance = new constructor();
84
+ let error; // not an instance of the constructor, create one ourself
85
+
86
+ if (!(instance instanceof constructor)) {
87
+ instance = Object.create(constructor.prototype);
88
+ } // alias legacy global flags attribute names when necessary
89
+
90
+
91
+ if (ctx.flags.dryRun) ctx.flags['dry-run'] = ctx.flags.dryRun; // legacy percyrc value
92
+
93
+ let percyrc = overrides => merge([ctx.percy.config, overrides, {
94
+ skipUploads: ctx.flags.debug,
95
+ dryRun: ctx.flags['dry-run'],
96
+ config: false
97
+ }], (path, prev, next) => // do not merge arrays
98
+ Array.isArray(next) && [path, next]); // augment legacy instance properties
99
+
100
+
101
+ Object.defineProperties(instance, {
102
+ isPercyEnabled: {
103
+ value: () => !!ctx.percy
104
+ },
105
+ error: {
106
+ value: msg => ctx.exit(1, msg)
107
+ },
108
+ parse: {
109
+ value: () => ctx
110
+ },
111
+ percyrc: {
112
+ value: percyrc
113
+ },
114
+ flags: {
115
+ value: ctx.flags
116
+ },
117
+ args: {
118
+ value: ctx.args
119
+ },
120
+ exit: {
121
+ value: ctx.exit
122
+ },
123
+ log: {
124
+ value: ctx.log
125
+ }
126
+ }); // legacy signal handling
127
+
128
+ let signals = ['SIGHUP', 'SIGINT', 'SIGTERM'];
129
+
130
+ let cleanup = () => {
131
+ var _instance$finally, _instance;
132
+
133
+ return (_instance$finally = (_instance = instance).finally) === null || _instance$finally === void 0 ? void 0 : _instance$finally.call(_instance, new Error('SIGTERM'));
134
+ };
135
+
136
+ for (let e of signals) process.on(e, cleanup); // run and handle legacy command
137
+
138
+
139
+ try {
140
+ yield instance.run();
141
+ } catch (err) {
142
+ throw error = err;
143
+ } finally {
144
+ var _instance$finally2, _instance2;
145
+
146
+ await ((_instance$finally2 = (_instance2 = instance).finally) === null || _instance$finally2 === void 0 ? void 0 : _instance$finally2.call(_instance2, error));
147
+
148
+ for (let e of signals) process.off(e, cleanup);
149
+ }
150
+ }); // throw legacy errors for test compatibility
151
+
152
+ return Object.defineProperties(args => runner(args).catch(e => {
153
+ throw new Error(`EEXIT: ${e.exitCode}`);
154
+ }), Object.getOwnPropertyDescriptors(runner));
155
+ } // Support the legacy static run method
156
+
157
+ command.run = function run(args) {
158
+ return legacyCommand('legacy', this)(args);
159
+ };
160
+
161
+ export default legacyCommand;