@percy/cli-command 1.0.0-beta.9 → 1.0.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/README.md CHANGED
@@ -1,60 +1,193 @@
1
1
  # @percy/cli-command
2
2
 
3
- The base command class that Percy CLI commands should extend from. Adds commonly used configuration
4
- options to Percy config files, a `#percyrc()` method for parsing config files, and other shared
5
- methods. Also provides common CLI flags along with oclif flag functions.
3
+ Percy CLI command parser and runner.
4
+
5
+ - [Usage](#usage)
6
+ - [Options](#options)
7
+ - [Args](#args)
8
+ - [Flags](#flags)
9
+ - [Percy](#percy)
6
10
 
7
11
  ## Usage
8
12
 
9
- ```js
10
- import PercyCommand, { flags } from '@percy/cli-command'
11
-
12
- export class PercyPlugin extends PercyCommand {
13
- static flags = {
14
- ...flags.logging,
15
- ...flags.discovery,
16
- ...flags.config
17
- }
18
-
19
- run() {
20
- let { args, flags } = this
21
- let { snapshot, discovery } = this.percyrc()
22
- // ...
23
- }
24
-
25
- finally() {
26
- // called after #run() and also on process termination events
27
- }
28
- }
13
+ The `command` function accepts a name, definition, and callback and returns a runner function that
14
+ accepts an array of command-line arguments.
15
+
16
+ ``` js
17
+ import command from '@percy/cli-command';
18
+
19
+ // example command runner
20
+ const example = command('example', {
21
+ description: 'Example command description',
22
+ // positional arguments
23
+ args: [
24
+ { name: 'one' }
25
+ ],
26
+ // flags and options
27
+ flags: [
28
+ { name: 'foo' }, // boolean by default
29
+ { name: 'bar', type: 'string' }
30
+ ],
31
+ // shows in help output
32
+ examples: [
33
+ // $0 will be replaced by the command name
34
+ '$0 --foo --bar baz qux'
35
+ ]
36
+ }, ({ flags, args }) => {
37
+ // see below for other available properties
38
+ console.log({ flags, args });
39
+ });
40
+
41
+ // remove the script name from argv
42
+ example(process.argv.slice(2));
29
43
  ```
30
44
 
31
- ## Percy Configuration
32
-
33
- This CLI plugin adds the following Percy configuration options that may be commonly used throughout
34
- other Percy CLI plugins (defaults shown).
35
-
36
- ``` yaml
37
- version: 2
38
- snapshot:
39
- widths: [375, 1280]
40
- minHeight: 1024
41
- percyCSS: ''
42
- requestHeaders: {}
43
- discovery:
44
- allowedHostnames: []
45
- networkIdleTimeout: 100
46
- disableAssetCache: false
47
- concurrency: 5
45
+ ``` shell
46
+ $ node ./example --foo --bar baz qux
47
+ { flags: { foo: true, bar: 'baz' }, args: { one: 'qux' } }
48
48
  ```
49
49
 
50
- ### Mapping CLI Flags
50
+ ## Options
51
+
52
+ Only a command `name` is required. However, without any other options, a command will only show the
53
+ default help output with global flags. A command's `callback` will be called when the resulting
54
+ command runner is invoked with an array of command-line arguments. How those arguments are
55
+ interpreted and parsed is defined by the command's `definition`.
56
+
57
+ - `name` — The command name shown in help output, also used to match nested commands.
58
+ - `definition` — A command definition object defines various options to control help output, the
59
+ arguments parser, and other percy options.
60
+ - `description` — The command description shown in help output.
61
+ - `usage` — Custom command usage shown in help output.
62
+ - `examples` — An array of example strings to show in help output.
63
+ - `version` — A version string to display via a `-V, --version` flag.
64
+ - `commands` — An array or function returning an array of nested subcommands.
65
+ - `flags` — Optional flag arguments accepted by the command ([see below](#flags)).
66
+ - `args` — Positional arguments accepted by the command ([see below](#args)).
67
+ - `percy` — Enable creation of a core percy instance ([see below](#percy)).
68
+ - `config` — Automatic percy config registration ([see below](#percy)).
69
+ - `loose` — Push unknown arguments to `argv`. A string value will log as a warning when unknown
70
+ arguments are first encountered by the parser.
71
+ - `exitOnError` — Call `process.exit()` if the command encounters an error while running.
72
+ - `callback` — The command callback invoked after the runner is called with command-line arguments
73
+ and after those arguments are interpreted by the internal parser.
74
+
75
+ The `callback` argument can be a function, an async function, or an async generator function. Given
76
+ an async generator function, the command becomes cancelable via process interruption and is able to
77
+ clean up after itself using traditional try-catch error handling.
78
+
79
+ The `callback` function is invoked with a single argument containing various contextual properties
80
+ according to the command definition:
81
+
82
+ - `flags` — An object containing key-value pairs of flag arguments interpreted by the parser as
83
+ determined by the flag's respective definition ([see below](#flags)).
84
+ - `args` — An object containing key-value pairs of positional arguments interpreted by the parser as
85
+ determined by the arg's respective definition ([see below](#args)).
86
+ - `argv` — An array of unknown arguments that were not parsed due to either the `loose` option or
87
+ the presence of an end-of-arguments delimiter (`--`).
88
+ - `percy` — An instance of `@percy/core` only present when a corresponding `percy` definition option
89
+ is provided ([see below](#percy)).
90
+ - `log` — A `@percy/logger` group namespaced under the command's `name`. The initial loglevel is
91
+ optionally determined by various global logging flags (`--verbose`, `--silent`, `--quiet`).
92
+ - `exit` — Utility function to stop the execution of a command from within the runner. Accepts
93
+ an exit code (default `1`) and an exit reason (default `'EEXIT: ${exitCode}'`). The function will
94
+ throw an error which can be handled, or let bubble where the presence of `exitOnError` will cause
95
+ the process to exit with the provided exit code.
96
+
97
+ ### Flags
98
+
99
+ Each flag definition must contain either a `name` or a `short` option. By default, all flags will be
100
+ interpreted as boolean flags and will produce an error if a value is provided. Setting the `type`
101
+ option to anything other than `'boolean'` will allow providing command-line values and error if one
102
+ _is not_ provided.
103
+
104
+ - `name` — The flag name (i.e. `--flag-name`).
105
+ - `short` — The flag short character (i.e. `-s`).
106
+ - `type` — The flag type as displayed in help output.
107
+ - `default` — The flag's default value when not provided.
108
+ - `parse` — A function used to parse any provided flag value.
109
+ - `validate` — A function that should throw an error when the provided flag value is invalid.
110
+ - `attribute` — A string or function returning a string to set the value at on the `callback`
111
+ argument's `flags` property. Defaults to the camelcase `name` or `short` character.
112
+ - `percyrc` — A string to map this flag to percy config options ([see below](#percy)).
113
+ - `deprecated` — A boolean, string, or tuple used to warn about a deprecated flag and optionally map
114
+ it to an alternate flag ([see below](#deprecations)).
115
+
116
+ ### Args
117
+
118
+ Positional args correspond to their definition index, so the arg definition at index `0` will match
119
+ the first provided command-line argument that is not a flag or subcommand. By default, positional
120
+ args are optional, but can be configured to be required or have default values.
51
121
 
52
- The `#percyrc()` method will parse command flags for Percy configuration mappings as determined by a
53
- `percyrc` flag configuration option.
122
+ - `name` — The arg name shown in help output.
123
+ - `required` — Indicate that this flag is required, which causes an error when not provided.
124
+ - `default` — The arg's default value when not provided.
125
+ - `parse` — A function used to parse the provided argument.
126
+ - `validate` — A function that should throw an error when the provided argument is invalid.
127
+ - `attribute` — A string or function returning a string to set the value at on the `callback`
128
+ argument's `args` property. Defaults to camelcase `name`.
129
+ - `percyrc` — A string to map this arg to percy config options ([see below](#percy)).
130
+ - `deprecated` — A boolean, string, or tuple used to warn about a deprecated arg and optionally map
131
+ it to a flag ([see below](#deprecations)).
132
+
133
+ #### Deprecations
134
+
135
+ When the `deprecated` option is `true` and the flag or positional arg has been provided by
136
+ command-line arguments, a deprecation warning will be logged. A string value indicates the version
137
+ in which this argument will be removed and is reflected in the deprecation message.
138
+
139
+ <details>
140
+ <summary>Remapping deprecated arguments</summary><br>
141
+
142
+ When providing a tuple, the version is the first option of the tuple, while the second option can be
143
+ an alternate flag to use, or a recommendation message to display when the deprecation warning is
144
+ logged. Given an alternate flag, the value will be automatically mapped to the corresponding flag's
145
+ attribute name on the `callback` argument's `flags` property.
54
146
 
55
147
  ``` js
56
- 'allowed-hostnames': flags.string({
57
- // maps the --allowed-hostnames flag to the corresponding config option
58
- percyrc: 'discovery.allowedHostnames'
59
- })
148
+ { name: 'foo', deprecated: true }
149
+ // [percy] Warning: The '--foo' option will be removed in a future release.
150
+
151
+ { name: 'foo', deprecated: 'v2.0.0' }
152
+ // [percy] Warning: The '--foo' option will be removed in v2.0.0.
153
+
154
+ { name: 'foo', deprecated: ['v2.0.0', '--bar'] }
155
+ // [percy] Warning: The '--foo' option will be removed in v2.0.0. Use '--bar' instead.
156
+
157
+ { name: 'foo', deprecated: ['v2.0.0', 'Use the config file option instead.'] }
158
+ // [percy] Warning: The '--foo' option will be removed in v2.0.0. Use the config file option instead.
60
159
  ```
160
+ </details>
161
+
162
+ ### Percy
163
+
164
+ The `percy` definition option accepts an object consisting of `@percy/core` options. The presence of
165
+ this option will add shared percy flags to accepted command-line arguments and provide the command
166
+ callback with a percy instance initialized with the provided options.
167
+
168
+ When the `percy` definition option is `true`, shared percy flags will not be accepted, but a default
169
+ percy instance will still be provided to the command callback when run. Regardless of the `percy`
170
+ option, if the environment variable `PERCY_ENABLE` is `0`, the callback _will not_ receive a percy
171
+ instance (and can act accordingly).
172
+
173
+ - `percy` — Enables creation of a `@percy/core` instance initialized with provided options.
174
+ - `discoveryFlags` — When `false` prevents percy discovery flags from being accepted, but will
175
+ still allow other percy flags such as `--config` and `--dry-run`.
176
+ - `...` — All other options are passed along to the new percy instance.
177
+
178
+ #### Config
179
+
180
+ Acceptable percy config file options can be extended before the file is loaded via
181
+ `PercyConfig.addSchema()` and `PercyConfig.addMigration()`. These functions can be called before the
182
+ command is run to achieve the same effect as including them in the command definition. However, the
183
+ presence of a `percy` definition option will also signal `@percy/core` config schemas and migrations
184
+ to be automatically registered before the following options are registered.
185
+
186
+ - `config` — Percy config schemas and migrations to register while creating the command runner.
187
+ - `schemas` — An array of percy config schemas to automatically register.
188
+ - `migrations` — An array of percy config migrations to automatically register.
189
+
190
+ Additionally, flags and positional arguments that define a `percyrc` option will have their
191
+ associated values mapped to a corresponding `percy` property that is used when initializing a
192
+ `@percy/core` instance. Provided options are validated against config schemas and are made available
193
+ on the command callback argument's `percy.config` property.
package/bin/readme ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = await import('fs');
4
+ const path = await import('path');
5
+ const { formatHelp } = await import('../dist/help');
6
+
7
+ async function updateReadmeCommands(cwd = process.cwd()) {
8
+ let readmePath = path.join(cwd, 'README.md');
9
+ let pkgPath = path.join(cwd, 'package.json');
10
+ let sections = [];
11
+ let toc = [];
12
+
13
+ let pkg = JSON.parse(fs.readFileSync(pkgPath));
14
+ if (!pkg['@percy/cli']?.commands) return '';
15
+
16
+ for (let cmdPath of pkg['@percy/cli'].commands) {
17
+ let cmdURL = url.pathToFileURL(path.join(CWD, cmdPath));
18
+ let { default: command } = await import(cmdURL.href);
19
+ command = { ...command, parent: { name: 'percy' } };
20
+
21
+ for (let cmd of [command, ...(command.definition.commands || [])]) {
22
+ if (!cmd.callback) continue;
23
+ if (cmd !== command) cmd = { ...cmd, parent: command };
24
+ if (cmd.parent.parent) cmd.name = `${cmd.parent.name}:${cmd.name}`;
25
+
26
+ let title = `\`percy ${cmd.name}\``;
27
+ let slug = title.slice(1, -1).replace(/\s/g, '-').replace(/:/g, '');
28
+ toc.push(`* [${title}](#${slug})`);
29
+
30
+ let help = await formatHelp(cmd);
31
+ help = help.replace(/^(.*)?(usage:.*)$/gis, '$1```\n$2```');
32
+ sections.push(`### ${title}\n\n${help}\n`);
33
+ }
34
+ }
35
+
36
+ fs.writeFileSync(readmePath, fs.readFileSync(readmePath, 'utf-8').replace(
37
+ /(<!-- commands -->)(.*)(<!-- commandsstop -->)/is,
38
+ `$1\n${[toc.join('\n'), sections.join('\n')].join('\n\n')}$3`
39
+ ));
40
+ }
41
+
42
+ updateReadmeCommands();
package/package.json CHANGED
@@ -1,32 +1,41 @@
1
1
  {
2
2
  "name": "@percy/cli-command",
3
- "version": "1.0.0-beta.9",
3
+ "version": "1.0.0",
4
4
  "license": "MIT",
5
- "main": "dist/index.js",
6
- "types": "types/index.d.ts",
7
- "files": [
8
- "dist",
9
- "types/index.d.ts"
10
- ],
11
- "scripts": {
12
- "build": "babel --root-mode upward src --out-dir dist",
13
- "lint": "eslint --ignore-path ../../.gitignore .",
14
- "test": "cross-env NODE_ENV=test mocha",
15
- "test:coverage": "nyc yarn test",
16
- "test:types": "tsd"
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/percy/cli",
8
+ "directory": "packages/cli-command"
17
9
  },
18
10
  "publishConfig": {
19
11
  "access": "public"
20
12
  },
21
- "mocha": {
22
- "require": "../../scripts/babel-register"
13
+ "files": [
14
+ "./dist"
15
+ ],
16
+ "engines": {
17
+ "node": ">=14"
18
+ },
19
+ "bin": {
20
+ "percy-cli-readme": "./bin/readme"
21
+ },
22
+ "main": "./dist/index.js",
23
+ "type": "module",
24
+ "exports": {
25
+ ".": "./dist/index.js",
26
+ "./utils": "./dist/utils.js",
27
+ "./test/helpers": "./test/helpers.js"
28
+ },
29
+ "scripts": {
30
+ "build": "node ../../scripts/build",
31
+ "lint": "eslint --ignore-path ../../.gitignore .",
32
+ "test": "node ../../scripts/test",
33
+ "test:coverage": "yarn test --coverage"
23
34
  },
24
35
  "dependencies": {
25
- "@oclif/command": "^1.5.19",
26
- "@oclif/config": "^1.14.0",
27
- "@oclif/plugin-help": "^3.1.0",
28
- "@percy/config": "^1.0.0-beta.9",
29
- "@percy/logger": "^1.0.0-beta.9"
36
+ "@percy/config": "1.0.0",
37
+ "@percy/core": "1.0.0",
38
+ "@percy/logger": "1.0.0"
30
39
  },
31
- "gitHead": "57a2eeb90c7f5cdf8827c78be1e5c12df581f4b5"
40
+ "gitHead": "6df509421a60144e4f9f5d59dc57a5675372a0b2"
32
41
  }
package/dist/command.js DELETED
@@ -1,83 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
-
8
- var _command = _interopRequireDefault(require("@oclif/command"));
9
-
10
- var _config = _interopRequireDefault(require("@percy/config"));
11
-
12
- var _logger = _interopRequireDefault(require("@percy/logger"));
13
-
14
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15
-
16
- // The PercyCommand class that all Percy CLI commands should extend
17
- // from. Provides common #init() and #catch() methods and provides other methods
18
- // for loading configuration and checking if Percy is enabled.
19
- class PercyCommand extends _command.default {
20
- // Initialize flags, args, the loglevel, and attach process handlers to allow
21
- // commands to seemlessly cleanup on interupt or termination
22
- init() {
23
- let {
24
- args,
25
- flags
26
- } = this.parse();
27
- this.flags = flags;
28
- this.args = args; // sets the log level from verbose, quiet, and silent flags
29
-
30
- _logger.default.loglevel('info', flags); // ensure cleanup is always performed
31
-
32
-
33
- let cleanup = () => this.finally();
34
-
35
- process.on('SIGHUP', cleanup);
36
- process.on('SIGINT', cleanup);
37
- process.on('SIGTERM', cleanup);
38
- } // Log errors using the Percy logger
39
-
40
-
41
- async catch(err) {
42
- try {
43
- // real errors will bubble
44
- await super.catch(err);
45
- } catch (err) {
46
- // oclif exit method actually throws an error, don't log it
47
- if (!err.oclif || err.code !== 'EEXIT') _logger.default.error(err);
48
- throw err;
49
- }
50
- } // Returns true or false if Percy has not been disabled
51
-
52
-
53
- isPercyEnabled() {
54
- return process.env.PERCY_ENABLE !== '0';
55
- } // Parses command flags and maps them to config options according to their
56
- // respective `percyrc` parameter. The flag input is then merged with options
57
- // loaded from a config file and default config options. The PERCY_TOKEN
58
- // environment variable is also included as a convenience.
59
-
60
-
61
- percyrc() {
62
- let flags = Object.entries(this.constructor.flags);
63
- let overrides = flags.reduce((conf, [name, flag]) => {
64
- var _flag$percyrc$split$r, _flag$percyrc;
65
-
66
- return (_flag$percyrc$split$r = (_flag$percyrc = flag.percyrc) === null || _flag$percyrc === void 0 ? void 0 : _flag$percyrc.split('.').reduce((target, key, i, paths) => {
67
- var _target$key;
68
-
69
- let last = i === paths.length - 1;
70
- target[key] = last ? this.flags[name] : (_target$key = target[key]) !== null && _target$key !== void 0 ? _target$key : {};
71
- return last ? conf : target[key];
72
- }, conf)) !== null && _flag$percyrc$split$r !== void 0 ? _flag$percyrc$split$r : conf;
73
- }, {}); // will also validate config and log warnings
74
-
75
- return _config.default.load({
76
- path: this.flags.config,
77
- overrides
78
- });
79
- }
80
-
81
- }
82
-
83
- exports.default = PercyCommand;
package/dist/flags.js DELETED
@@ -1,58 +0,0 @@
1
- "use strict";
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`
52
-
53
- var _default = { ..._command.flags,
54
- logging,
55
- discovery,
56
- config
57
- };
58
- exports.default = _default;
package/dist/index.js DELETED
@@ -1,23 +0,0 @@
1
- "use strict";
2
-
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 }; }
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-asset-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
- }