@percy/cli 1.0.0 → 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/bin/run.cmd ADDED
@@ -0,0 +1,3 @@
1
+ @echo off
2
+
3
+ node "%~dp0\run" %*
@@ -0,0 +1,162 @@
1
+ import os from 'os';
2
+ import fs from 'fs';
3
+ import url from 'url';
4
+ import path from 'path';
5
+ import Module from 'module';
6
+ import { command, legacyCommand, logger } from '@percy/cli-command'; // Helper to simplify reducing async functions
7
+
8
+ async function reduceAsync(iter, reducer, accum = []) {
9
+ for (let i of iter) accum = await reducer(accum, i);
10
+
11
+ return accum;
12
+ } // Helper to read and reduce files within a directory
13
+
14
+
15
+ function reduceFiles(dir, reducer) {
16
+ return reduceAsync(fs.readdirSync(dir, {
17
+ withFileTypes: true
18
+ }), reducer);
19
+ } // Returns the paths of potential percy packages found within node_modules
20
+
21
+
22
+ function findModulePackages(dir) {
23
+ try {
24
+ // not given node_modules or a directory that contains node_modules, look up
25
+ if (path.basename(dir) !== 'node_modules') {
26
+ let modulesPath = path.join(dir, 'node_modules');
27
+ let next = fs.existsSync(modulesPath) ? modulesPath : path.dirname(dir);
28
+ if (next === dir || next === os.homedir()) return [];
29
+ return findModulePackages(next);
30
+ } // given node modules, look for percy packages
31
+
32
+
33
+ return reduceFiles(dir, async (roots, file) => {
34
+ let rootPath = path.join(dir, file.name);
35
+
36
+ if (file.name === '@percy') {
37
+ return roots.concat(await reduceFiles(rootPath, (dirs, f) => // specifically protect against files to allow linked directories
38
+ f.isFile() ? dirs : dirs.concat(path.join(rootPath, f.name)), []));
39
+ } else if (file.name.startsWith('percy-cli-')) {
40
+ return roots.concat(rootPath);
41
+ } else {
42
+ return roots;
43
+ }
44
+ }, []);
45
+ } catch (error) {
46
+ logger('cli:plugins').debug(error);
47
+ return [];
48
+ }
49
+ } // Used by `findPnpPackages` to filter Percy CLI plugins
50
+
51
+
52
+ const PERCY_PKG_REG = /^(@percy\/|percy-cli-)/; // Returns the paths of potential percy packages found within yarn's pnp system
53
+
54
+ function findPnpPackages(dir) {
55
+ var _Module$findPnpApi;
56
+
57
+ let pnpapi = (_Module$findPnpApi = Module.findPnpApi) === null || _Module$findPnpApi === void 0 ? void 0 : _Module$findPnpApi.call(Module, `${dir}/`);
58
+ let pkgLoc = pnpapi === null || pnpapi === void 0 ? void 0 : pnpapi.findPackageLocator(`${dir}/`);
59
+ let pkgInfo = pkgLoc && (pnpapi === null || pnpapi === void 0 ? void 0 : pnpapi.getPackageInformation(pkgLoc));
60
+ let pkgDeps = (pkgInfo === null || pkgInfo === void 0 ? void 0 : pkgInfo.packageDependencies.entries()) ?? [];
61
+ return Array.from(pkgDeps).reduce((roots, [name, ref]) => {
62
+ if (!ref || !PERCY_PKG_REG.test(name)) return roots;
63
+ let depLoc = pnpapi.getLocator(name, ref);
64
+ let depInfo = pnpapi.getPackageInformation(depLoc);
65
+ return roots.concat(depInfo.packageLocation);
66
+ }, []);
67
+ } // Helper to import and wrap legacy percy commands for reverse compatibility
68
+
69
+
70
+ function importLegacyCommands(commandsPath) {
71
+ return reduceFiles(commandsPath, async (cmds, file) => {
72
+ let filepath = path.join(commandsPath, file.name);
73
+ let {
74
+ name
75
+ } = path.parse(file.name);
76
+
77
+ if (file.isDirectory()) {
78
+ // recursively import nested commands and find the index command
79
+ let commands = await importLegacyCommands(filepath);
80
+ let index = commands.findIndex(cmd => cmd.name === 'index'); // modify or create an index command to hold nested commands
81
+
82
+ index = ~index ? commands.splice(index, 1)[0] : command();
83
+ Object.defineProperty(index, 'name', {
84
+ value: name
85
+ });
86
+ index.definition.commands = commands;
87
+ return cmds.concat(index);
88
+ } else {
89
+ // find and wrap the command exported by the module
90
+ let exports = Object.values(await import(url.pathToFileURL(filepath).href));
91
+ let cmd = exports.find(e => {
92
+ var _e$prototype;
93
+
94
+ return typeof (e === null || e === void 0 ? void 0 : (_e$prototype = e.prototype) === null || _e$prototype === void 0 ? void 0 : _e$prototype.run) === 'function';
95
+ });
96
+ return cmd ? cmds.concat(legacyCommand(name, cmd)) : cmds;
97
+ }
98
+ });
99
+ } // Imports and returns compatibile CLI commands from various sources
100
+
101
+
102
+ export async function importCommands() {
103
+ let root = path.resolve(url.fileURLToPath(import.meta.url), '../..'); // start with a set to get built-in deduplication
104
+
105
+ let cmdPkgs = await reduceAsync(new Set([// find included dependencies
106
+ root, // find potential sibling packages
107
+ path.join(root, '..'), // find any current project dependencies
108
+ process.cwd()]), async (roots, dir) => {
109
+ roots.push(...(await findModulePackages(dir)));
110
+ roots.push(...(await findPnpPackages(dir)));
111
+ return roots;
112
+ }); // reduce found packages to functions which import cli commands
113
+
114
+ let cmdImports = await reduceAsync(cmdPkgs, async (pkgs, pkgPath) => {
115
+ var _pkg$oclif, _pkg$PercyCli;
116
+
117
+ let pkg = JSON.parse(fs.readFileSync(path.join(pkgPath, 'package.json'))); // do not include self
118
+
119
+ if (pkg.name === '@percy/cli') return pkgs; // support legacy oclif percy commands
120
+
121
+ if (((_pkg$oclif = pkg.oclif) === null || _pkg$oclif === void 0 ? void 0 : _pkg$oclif.bin) === 'percy') {
122
+ pkgs.set(pkg.name, async () => {
123
+ var _pkg$oclif$hooks;
124
+
125
+ if ((_pkg$oclif$hooks = pkg.oclif.hooks) !== null && _pkg$oclif$hooks !== void 0 && _pkg$oclif$hooks.init) {
126
+ let initPath = path.join(pkgPath, pkg.oclif.hooks.init);
127
+ let init = await import(url.pathToFileURL(initPath).href);
128
+ await init.default();
129
+ }
130
+
131
+ if (pkg.oclif.commands) {
132
+ let commandsPath = path.join(pkgPath, pkg.oclif.commands);
133
+ return importLegacyCommands(commandsPath);
134
+ }
135
+
136
+ return [];
137
+ });
138
+ } // overwrite any found package of the same name
139
+
140
+
141
+ if ((_pkg$PercyCli = pkg['@percy/cli']) !== null && _pkg$PercyCli !== void 0 && _pkg$PercyCli.commands) {
142
+ pkgs.set(pkg.name, () => Promise.all(pkg['@percy/cli'].commands.map(async cmdPath => {
143
+ var _module$default;
144
+
145
+ let modulePath = path.join(pkgPath, cmdPath);
146
+ let module = await import(url.pathToFileURL(modulePath).href);
147
+ (_module$default = module.default).packageInformation || (_module$default.packageInformation = pkg);
148
+ return module.default;
149
+ })));
150
+ }
151
+
152
+ return pkgs;
153
+ }, new Map()); // actually import found commands
154
+
155
+ let cmds = await reduceAsync(cmdImports.values(), async (cmds, importCmds) => cmds.concat(await importCmds())); // sort standalone commands before command topics
156
+
157
+ return cmds.sort((a, b) => {
158
+ if (a.callback && !b.callback) return -1;
159
+ if (b.callback && !a.callback) return 1;
160
+ return a.name.localeCompare(b.name);
161
+ });
162
+ }
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { default, percy } from './percy.js';
2
+ export { checkForUpdate } from './update.js';
package/dist/percy.js ADDED
@@ -0,0 +1,10 @@
1
+ import command from '@percy/cli-command';
2
+ import { getPackageJSON } from '@percy/cli-command/utils';
3
+ import { importCommands } from './commands.js';
4
+ const pkg = getPackageJSON(import.meta.url);
5
+ export const percy = command('percy', {
6
+ version: `${pkg.name} ${pkg.version}`,
7
+ commands: () => importCommands(),
8
+ exitOnError: true
9
+ });
10
+ export default percy;
package/dist/update.js ADDED
@@ -0,0 +1,100 @@
1
+ import fs from 'fs';
2
+ import url from 'url';
3
+ import path from 'path';
4
+ import logger from '@percy/logger';
5
+ import { colors } from '@percy/logger/utils';
6
+ import { getPackageJSON } from '@percy/cli-command/utils'; // filepath where the cache will be read and written to
7
+
8
+ const CACHE_FILE = path.resolve(url.fileURLToPath(import.meta.url), '../../.releases'); // max age the cache should be used for (3 days)
9
+
10
+ const CACHE_MAX_AGE = 3 * 24 * 60 * 60 * 1000; // Safely read from CACHE_FILE and return an object containing `data` mirroring what was previously
11
+ // written using `writeToCache(data)`. An empty object is returned when older than CACHE_MAX_AGE,
12
+ // and an `error` will be present if one was encountered.
13
+
14
+ function readFromCache() {
15
+ let cached = {};
16
+
17
+ try {
18
+ if (fs.existsSync(CACHE_FILE)) {
19
+ let {
20
+ createdAt,
21
+ data
22
+ } = JSON.parse(fs.readFileSync(CACHE_FILE));
23
+ if (Date.now() - createdAt < CACHE_MAX_AGE) cached.data = data;
24
+ }
25
+ } catch (error) {
26
+ let log = logger('cli:update:cache');
27
+ log.debug('Unable to read from cache');
28
+ log.debug(cached.error = error);
29
+ }
30
+
31
+ return cached;
32
+ } // Safely write data to CACHE_FILE with the current timestamp.
33
+
34
+
35
+ function writeToCache(data) {
36
+ try {
37
+ fs.writeFileSync(CACHE_FILE, JSON.stringify({
38
+ createdAt: Date.now(),
39
+ data
40
+ }));
41
+ } catch (error) {
42
+ let log = logger('cli:update:cache');
43
+ log.debug('Unable to write to cache');
44
+ log.debug(error);
45
+ }
46
+ } // Fetch and return release information for @percy/cli.
47
+
48
+
49
+ async function fetchReleases(pkg) {
50
+ let {
51
+ request
52
+ } = await import('@percy/client/utils'); // fetch releases from the github api without retries
53
+
54
+ let api = 'https://api.github.com/repos/percy/cli/releases';
55
+ let data = await request(api, {
56
+ headers: {
57
+ 'User-Agent': pkg.name
58
+ },
59
+ retries: 0
60
+ }); // return relevant information
61
+
62
+ return data.map(r => ({
63
+ tag: r.tag_name,
64
+ prerelease: r.prerelease
65
+ }));
66
+ } // Check for updates by comparing latest releases with the current version. The result of the check
67
+ // is cached to speed up subsequent CLI usage.
68
+
69
+
70
+ export async function checkForUpdate() {
71
+ let {
72
+ data: releases,
73
+ error: cacheError
74
+ } = readFromCache();
75
+ let pkg = getPackageJSON(import.meta.url);
76
+ let log = logger('cli:update');
77
+
78
+ try {
79
+ // request new release information if needed
80
+ if (!releases) {
81
+ releases = await fetchReleases(pkg);
82
+ if (!cacheError) writeToCache(releases, log);
83
+ } // check the current package version against released versions
84
+
85
+
86
+ let versions = releases.map(r => r.tag.substr(1));
87
+ let age = versions.indexOf(pkg.version); // a new version is available
88
+
89
+ if (age !== 0) {
90
+ let range = `${colors.red(pkg.version)} -> ${colors.green(versions[0])}`;
91
+ log.stderr.write('\n');
92
+ log.warn(`${age > 0 && age < 10 ? 'A new version of @percy/cli is available!' : 'Heads up! The current version of @percy/cli is more than 10 releases behind!'} ${range}`);
93
+ log.stderr.write('\n');
94
+ }
95
+ } catch (err) {
96
+ log.debug('Unable to check for updates');
97
+ log.debug(err);
98
+ }
99
+ }
100
+ export default checkForUpdate;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@percy/cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -11,8 +11,8 @@
11
11
  "access": "public"
12
12
  },
13
13
  "files": [
14
- "./bin",
15
- "./dist"
14
+ "bin",
15
+ "dist"
16
16
  ],
17
17
  "engines": {
18
18
  "node": ">=14"
@@ -30,14 +30,14 @@
30
30
  "test:coverage": "yarn test --coverage"
31
31
  },
32
32
  "dependencies": {
33
- "@percy/cli-build": "1.0.0",
34
- "@percy/cli-command": "1.0.0",
35
- "@percy/cli-config": "1.0.0",
36
- "@percy/cli-exec": "1.0.0",
37
- "@percy/cli-snapshot": "1.0.0",
38
- "@percy/cli-upload": "1.0.0",
39
- "@percy/client": "1.0.0",
40
- "@percy/logger": "1.0.0"
33
+ "@percy/cli-build": "1.0.1",
34
+ "@percy/cli-command": "1.0.1",
35
+ "@percy/cli-config": "1.0.1",
36
+ "@percy/cli-exec": "1.0.1",
37
+ "@percy/cli-snapshot": "1.0.1",
38
+ "@percy/cli-upload": "1.0.1",
39
+ "@percy/client": "1.0.1",
40
+ "@percy/logger": "1.0.1"
41
41
  },
42
- "gitHead": "6df509421a60144e4f9f5d59dc57a5675372a0b2"
42
+ "gitHead": "38917e6027299d6cd86008e2ccd005d90bbf89c0"
43
43
  }