diff --git a/packages/cli-snapshot/package.json b/packages/cli-snapshot/package.json index 2cd5e0e12..4f226786c 100644 --- a/packages/cli-snapshot/package.json +++ b/packages/cli-snapshot/package.json @@ -33,10 +33,6 @@ "@percy/cli-command": "1.0.0-beta.76", "@percy/config": "1.0.0-beta.76", "@percy/core": "1.0.0-beta.76", - "globby": "^11.0.4", - "path-to-regexp": "^6.2.0", - "picomatch": "^2.3.0", - "serve-handler": "^6.1.3", "yaml": "^1.10.0" } } diff --git a/packages/cli-snapshot/src/config.js b/packages/cli-snapshot/src/config.js index b066f0d6a..d1f260296 100644 --- a/packages/cli-snapshot/src/config.js +++ b/packages/cli-snapshot/src/config.js @@ -8,7 +8,7 @@ export const configSchema = { baseUrl: { $ref: '/snapshot/server#/properties/baseUrl' }, cleanUrls: { $ref: '/snapshot/server#/properties/cleanUrls' }, rewrites: { $ref: '/snapshot/server#/properties/rewrites' }, - overrides: { $ref: '/snapshot#/$defs/options' } + options: { $ref: '/snapshot#/$defs/options' } } }, sitemap: { @@ -16,20 +16,11 @@ export const configSchema = { $ref: '/snapshot#/$defs/filter', unevaluatedProperties: false, properties: { - overrides: { $ref: '/snapshot#/$defs/options' } + options: { $ref: '/snapshot#/$defs/options' } } } }; -// Snapshots file schema -export const snapshotsFileSchema = { - $id: '/snapshot/file', - oneOf: [ - { $ref: '/snapshot#/$defs/snapshots' }, - { $ref: '/snapshot/list' } - ] -}; - export function configMigration(config, util) { /* eslint-disable curly */ if (config.version < 2) { @@ -43,9 +34,12 @@ export function configMigration(config, util) { // static files and ignore options were renamed util.deprecate('static.files', { map: 'static.include', ...notice }); util.deprecate('static.ignore', { map: 'static.exclude', ...notice }); + // static and sitemap option overrides were renamed + util.deprecate('static.overrides', { map: 'static.options', ...notice }); + util.deprecate('sitemap.overrides', { map: 'sitemap.options', ...notice }); - for (let i in (config.static?.overrides || [])) { - let k = `static.overrides[${i}]`; + for (let i in (config.static?.options || [])) { + let k = `static.options[${i}]`; util.deprecate(`${k}.files`, { map: `${k}.include`, ...notice }); util.deprecate(`${k}.ignore`, { map: `${k}.exclude`, ...notice }); } diff --git a/packages/cli-snapshot/src/file.js b/packages/cli-snapshot/src/file.js deleted file mode 100644 index 859d8b2b0..000000000 --- a/packages/cli-snapshot/src/file.js +++ /dev/null @@ -1,45 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import PercyConfig from '@percy/config'; -import { withDefaults, snapshotMatches } from './utils'; - -// Loads snapshots from a js, json, or yaml file. -export async function loadSnapshotsFile(file, flags, invalid) { - let ext = path.extname(file); - let config = {}; - - // load snapshots file - if (ext === '.js') { - ({ default: config } = await import(path.resolve(file))); - if (typeof config === 'function') config = await config(); - } else if (ext === '.json') { - config = JSON.parse(fs.readFileSync(file, { encoding: 'utf-8' })); - } else if (ext.match(/\.ya?ml$/)) { - let YAML = await import('yaml'); - config = YAML.parse(fs.readFileSync(file, { encoding: 'utf-8' })); - } else { - throw new Error(`Unsupported filetype: ${file}`); - } - - // validate base-url before config options - if (flags.baseUrl && !flags.baseUrl.startsWith('http')) { - throw new Error('The base-url must include a protocol ' + ( - 'and hostname when providing a list of snapshots')); - } - - // validate snapshot config options - PercyConfig.validate(config, '/snapshot/file')?.forEach(invalid); - - // flags override config options - let { baseUrl, include, exclude } = { ...config, ...flags }; - - // support config that only contains a list of snapshots - return (Array.isArray(config) ? config : config.snapshots || []) - .reduce((snapshots, snap) => { - // reduce matching snapshots with default options - snap = withDefaults(snap, { host: baseUrl }); - - return snapshotMatches(snap, include, exclude) - ? snapshots.concat(snap) : snapshots; - }, []); -} diff --git a/packages/cli-snapshot/src/sitemap.js b/packages/cli-snapshot/src/sitemap.js deleted file mode 100644 index 408fcb1fd..000000000 --- a/packages/cli-snapshot/src/sitemap.js +++ /dev/null @@ -1,28 +0,0 @@ -import { mapStaticSnapshots } from './static'; -import { request } from '@percy/core/dist/utils'; - -// Fetches and maps sitemap URLs to snapshots. -export async function loadSitemapSnapshots(sitemapUrl, config) { - // fetch sitemap URLs - let urls = await request(sitemapUrl, (body, res) => { - // validate sitemap content-type - let [contentType] = res.headers['content-type'].split(';'); - - if (!/^(application|text)\/xml$/.test(contentType)) { - throw new Error('The sitemap must be an XML document, ' + ( - `but the content-type was "${contentType}"`)); - } - - // parse XML content into a list of URLs - let urls = body.match(/(?<=)(.*)(?=<\/loc>)/ig); - - // filter out duplicate URLs that differ by a trailing slash - return urls.filter((url, i) => { - let match = urls.indexOf(url.replace(/\/$/, '')); - return match === -1 || match === i; - }); - }); - - // map with inherited static options - return mapStaticSnapshots(urls, config); -} diff --git a/packages/cli-snapshot/src/snapshot.js b/packages/cli-snapshot/src/snapshot.js index dc2119303..48174e495 100644 --- a/packages/cli-snapshot/src/snapshot.js +++ b/packages/cli-snapshot/src/snapshot.js @@ -1,4 +1,5 @@ -import { existsSync, lstatSync } from 'fs'; +import fs from 'fs'; +import path from 'path'; import command from '@percy/cli-command'; import * as SnapshotConfig from './config'; import pkg from '../package.json'; @@ -12,8 +13,8 @@ export const snapshot = command('snapshot', { required: true, attribute: val => { if (/^https?:\/\//.test(val)) return 'sitemap'; - if (!existsSync(val)) throw new Error(`Not found: ${val}`); - return lstatSync(val).isDirectory() ? 'dir' : 'file'; + if (!fs.existsSync(val)) throw new Error(`Not found: ${val}`); + return fs.lstatSync(val).isDirectory() ? 'serve' : 'file'; } }], @@ -58,70 +59,91 @@ export const snapshot = command('snapshot', { ], percy: { + deferUploads: true, clientInfo: `${pkg.name}/${pkg.version}`, environmentInfo: `node/${process.version}` }, config: { - schemas: [ - SnapshotConfig.configSchema, - SnapshotConfig.snapshotsFileSchema - ], - migrations: [ - SnapshotConfig.configMigration - ] + schemas: [SnapshotConfig.configSchema], + migrations: [SnapshotConfig.configMigration] } }, async function*({ percy, args, flags, log, exit }) { - if (!percy) exit(0, 'Percy is disabled'); - - // set and validate static or sitemap config flags - if (args.dir || args.sitemap) { - percy.setConfig({ - [args.dir ? 'static' : 'sitemap']: { - include: flags.include, - exclude: flags.exclude - } - }); - } + let { include, exclude, baseUrl, cleanUrls } = flags; + let { file, serve, sitemap } = args; - // gather snapshots - let snapshots, server; + // parse and validate the --base-url flag after args are parsed + if (file || serve) baseUrl &&= parseBaseUrl(baseUrl, !!serve); + // only continue if percy is not disabled + if (!percy) exit(0, 'Percy is disabled'); try { - if (args.sitemap) { - let { loadSitemapSnapshots } = await import('./sitemap'); - let config = { ...percy.config.sitemap, ...flags }; - - snapshots = yield loadSitemapSnapshots(args.sitemap, config); - } else if (args.dir) { - let { serve, loadStaticSnapshots } = await import('./static'); - let config = { ...percy.config.static, ...flags }; - - server = yield serve(args.dir, config); - snapshots = yield loadStaticSnapshots(args.dir, { ...config, server }); - } else { - let { loadSnapshotsFile } = await import('./file'); - - snapshots = yield loadSnapshotsFile(args.file, flags, (invalid, i) => { - if (i === 0) log.warn('Invalid snapshot options:'); - log.warn(`- ${invalid.path}: ${invalid.message}`); - }); - } + let options; - if (!snapshots.length) { - exit(1, 'No snapshots found'); + /* istanbul ignore else: arg is required and always one of these */ + if (file) { + // load snapshots file + let snapshots = yield loadSnapshotFile(file); + // accept a config object instead of an array of snapshots + let config = Array.isArray(snapshots) ? { snapshots } : snapshots; + options = merge(config, { baseUrl, include, exclude }); + } else if (serve) { + // serve and snapshot a static directory + let config = { serve, cleanUrls, baseUrl, include, exclude }; + options = merge(percy.config.static, config); + } else if (sitemap) { + // fetch urls to snapshot from a sitemap + let config = { sitemap, include, exclude }; + options = merge(percy.config.sitemap, config); } - // start processing snapshots yield* percy.start(); - percy.snapshot(snapshots); + yield percy.snapshot(options); yield* percy.stop(); } catch (error) { await percy.stop(true); throw error; - } finally { - await server?.close(); } }); +// Validates the provided `--base-url` flag and returns a `baseUrl` string if valid. The flag is +// validated and parsed differently for static directories and snapshot files. +function parseBaseUrl(baseUrl, pathOnly) { + try { + let needsHost = pathOnly && baseUrl.startsWith('/'); + let url = new URL(baseUrl, needsHost ? 'http://localhost' : undefined); + return pathOnly ? url.pathname : url.href; + } catch (e) { + throw new Error("The '--base-url' flag must " + (pathOnly + ? 'start with a forward slash (/) when providing a static directory' + : 'include a protocol and hostname when providing a list of snapshots' + )); + } +} + +// Small shallow merge util that does not merge null or undefined values. +function merge(...objs) { + return objs.reduce((target, obj) => { + for (let k in obj) target[k] = obj[k] ?? target[k]; + return target; + }, {}); +} + +// Loads snapshot options from a js, json, or yaml file. +async function loadSnapshotFile(file) { + let ext = path.extname(file); + + if (ext === '.js') { + let { default: module } = await import(path.resolve(file)); + return typeof module === 'function' ? await module() : module; + } else if (ext === '.json') { + return JSON.parse(fs.readFileSync(file, { encoding: 'utf-8' })); + } else if (ext.match(/\.ya?ml$/)) { + let { parse } = await import('yaml'); + return parse(fs.readFileSync(file, { encoding: 'utf-8' })); + } else { + throw new Error(`Unsupported filetype: ${file}`); + } +} + export default snapshot; diff --git a/packages/cli-snapshot/src/static.js b/packages/cli-snapshot/src/static.js deleted file mode 100644 index e0b3e5b7a..000000000 --- a/packages/cli-snapshot/src/static.js +++ /dev/null @@ -1,126 +0,0 @@ -import path from 'path'; -import { createServer } from 'http'; -import serveHandler from 'serve-handler'; -import * as pathToRegexp from 'path-to-regexp'; - -import { - validURL, - withDefaults, - snapshotMatches -} from './utils'; - -// Transforms a source-destination map into an array of source-destination objects -function mapRewrites(map, arr) { - return Object.entries(map).reduce((r, [source, destination]) => { - return (r || []).concat({ source, destination }); - }, arr); -} - -// Serves a static directory with the provided options and returns an object containing adjusted -// rewrites (combined with any baseUrl), the server host, a close method, and the server -// instance. The `dryRun` option will prevent the server from actually starting. -export async function serve(dir, { - dryRun, - baseUrl, - cleanUrls, - rewrites = {} -}) { - let host = 'http://localhost'; - let connections = new Set(); - - // coerce any provided base-url into a base-url path - if (baseUrl && !baseUrl.startsWith('/')) { - baseUrl = validURL(baseUrl).path; - } - - // map rewrite options with the base-url - rewrites = mapRewrites(rewrites, baseUrl && [{ - source: path.posix.join(baseUrl, '/:path*'), - destination: '/:path*' - }]); - - // start the server - let server = !dryRun && await new Promise(resolve => { - let server = createServer((req, res) => serveHandler( - req, res, { public: dir, cleanUrls, rewrites } - )).listen(() => resolve(server)).on('connection', s => { - connections.add(s.on('close', () => connections.delete(s))); - }); - }); - - // easy clean up - let close = () => server && new Promise(resolve => { - /* istanbul ignore next: sometimes needed when connections are hanging */ - connections.forEach(s => s.destroy()); - server.close(resolve); - }); - - // add the port to the host and return - if (server) host += `:${server.address().port}`; - return { host, rewrites, server, close }; -} - -// Maps an array of snapshots or paths to options ready to pass along to the core snapshot -// method. Paths are normalized before overrides are conditionally applied via their own include and -// exclude options. Snapshot URLs are then rewritten accordingly before default options are applied, -// including prepending the appropriate host. The returned set of snapshot options are sorted and -// filtered by the top-level include and exclude options. -export function mapStaticSnapshots(snapshots, /* istanbul ignore next: safe defaults */ { - host, - include, - exclude, - cleanUrls, - rewrites = [], - overrides = [], - server -} = {}) { - // prioritize server properties - host = server?.host ?? host; - rewrites = server?.rewrites ?? mapRewrites(rewrites, []); - - // reduce rewrites into a single function - let applyRewrites = [{ - test: url => !/^(https?:\/)?\//.test(url) && url, - rewrite: url => path.posix.normalize(path.posix.join('/', url)) - }, ...rewrites.map(({ source, destination }) => ({ - test: pathToRegexp.match(destination), - rewrite: pathToRegexp.compile(source) - })), { - test: url => cleanUrls && url, - rewrite: url => url.replace(/(\/index)?\.html$/, '') - }].reduceRight((apply, { test, rewrite }) => snap => { - let res = test(snap.url ?? snap); - if (res) snap = rewrite(res.params ?? res); - return apply(snap); - }, s => s); - - // reduce overrides into a single function - let applyOverrides = overrides - .reduceRight((apply, { include, exclude, ...opts }) => snap => { - if (snapshotMatches(snap, include, exclude)) Object.assign(snap, opts); - return apply(snap); - }, s => s); - - // sort and reduce snapshots with overrides - return [...snapshots].sort().reduce((snapshots, snap) => { - snap = withDefaults(applyRewrites(snap), { host }); - - return snapshotMatches(snap, include, exclude) - ? snapshots.concat(applyOverrides(snap)) : snapshots; - }, []); -} - -// Serves a static directory and returns a list of snapshots. -export async function loadStaticSnapshots(dir, config) { - let { default: globby } = await import('globby'); - - // gather paths with globby, which only accepts string patterns - let isStr = s => typeof s === 'string'; - let strOr = (a, b) => a.length && a.every(isStr) ? a : b; - let files = strOr([].concat(config.include || []), '**/*.html'); - let ignore = strOr([].concat(config.exclude || []), []); - let paths = await globby(files, { cwd: dir, ignore }); - - // map snapshots from paths and config - return mapStaticSnapshots(paths, config); -} diff --git a/packages/cli-snapshot/src/utils.js b/packages/cli-snapshot/src/utils.js deleted file mode 100644 index 6283805d3..000000000 --- a/packages/cli-snapshot/src/utils.js +++ /dev/null @@ -1,65 +0,0 @@ -import picomatch from 'picomatch'; - -// used to deserialize regular expression strings -const RE_REGEXP = /^\/(.+)\/(\w+)?$/; - -// Throw a better error message for invalid urls -export function validURL(url, base) { - try { return new URL(url, base); } catch (e) { - throw new Error(`Invalid URL: ${e.input}`); - } -} - -// Mutates an options object to have normalized and default values -export function withDefaults(options, { host }) { - // allow URLs as the only option - if (typeof options === 'string') options = { url: options }; - - // validate URLs - let url = validURL(options.url, host); - - // default name to the url path - options.name ||= `${url.pathname}${url.search}${url.hash}`; - // normalize the snapshot url - options.url = url.href; - - return options; -} - -// Returns true or false if a snapshot matches the provided include and exclude predicates. A -// predicate can be an array of predicates, a regular expression, a glob pattern, or a function. -export function snapshotMatches(snapshot, include, exclude) { - if (!include && !exclude) return true; - - let test = (predicate, fallback) => { - if (predicate && typeof predicate === 'string') { - // snapshot matches a glob - let result = picomatch(predicate, { basename: true })(snapshot.name); - - // snapshot might match a string pattern - if (!result) { - try { - let [, parsed = predicate, flags] = RE_REGEXP.exec(predicate) || []; - result = new RegExp(parsed, flags).test(snapshot.name); - } catch {} - } - - return result; - } else if (predicate instanceof RegExp) { - // snapshot matches a regular expression - return predicate.test(snapshot.name); - } else if (typeof predicate === 'function') { - // advanced matching - return predicate(snapshot); - } else if (Array.isArray(predicate) && predicate.length) { - // array of predicates - return predicate.some(p => test(p)); - } else { - // default fallback - return fallback; - } - }; - - // not excluded or explicitly included - return !test(exclude, false) && test(include, true); -} diff --git a/packages/cli-snapshot/test/common.test.js b/packages/cli-snapshot/test/common.test.js index 23035e727..e4a594f8b 100644 --- a/packages/cli-snapshot/test/common.test.js +++ b/packages/cli-snapshot/test/common.test.js @@ -36,8 +36,12 @@ describe('percy snapshot', () => { it('errors when there are no snapshots to take', async () => { await expectAsync(snapshot(['./tmp'])).toBeRejected(); - expect(logger.stdout).toEqual([]); + expect(logger.stdout).toEqual([ + '[percy] Percy has started!', + '[percy] Stopping percy...' + ]); expect(logger.stderr).toEqual([ + '[percy] Build not created', '[percy] Error: No snapshots found' ]); }); diff --git a/packages/cli-snapshot/test/directory.test.js b/packages/cli-snapshot/test/directory.test.js index 1e0f70a91..1e6c2dbb1 100644 --- a/packages/cli-snapshot/test/directory.test.js +++ b/packages/cli-snapshot/test/directory.test.js @@ -45,7 +45,8 @@ describe('percy snapshot ', () => { expect(logger.stdout).toEqual([]); expect(logger.stderr).toEqual([ - '[percy] Error: Invalid URL: wrong' + '[percy] Error: The \'--base-url\' flag must start with a ' + + 'forward slash (/) when providing a static directory' ]); }); @@ -55,10 +56,10 @@ describe('percy snapshot ', () => { expect(logger.stderr).toEqual([]); expect(logger.stdout).toEqual(jasmine.arrayContaining([ '[percy] Percy has started!', - '[percy] Processing 3 snapshots...', '[percy] Snapshot taken: /test-1.html', '[percy] Snapshot taken: /test-2.html', '[percy] Snapshot taken: /test-3.html', + '[percy] Uploading 3 snapshots...', '[percy] Finalized build #1: https://percy.io/test/test/123' ])); }); @@ -69,11 +70,11 @@ describe('percy snapshot ', () => { expect(logger.stderr).toEqual([]); expect(logger.stdout).toEqual(jasmine.arrayContaining([ '[percy] Percy has started!', - '[percy] Processing 4 snapshots...', '[percy] Snapshot taken: /base/test-1.html', '[percy] Snapshot taken: /base/test-2.html', '[percy] Snapshot taken: /base/test-3.html', '[percy] Snapshot taken: /base/test-index/index.html', + '[percy] Uploading 4 snapshots...', '[percy] Finalized build #1: https://percy.io/test/test/123' ])); }); @@ -98,7 +99,7 @@ describe('percy snapshot ', () => { fs.writeFileSync('.percy.yml', [ 'version: 2', 'static:', - ' overrides:', + ' options:', ' - additionalSnapshots:', ' - suffix: " (2)"', ' - include: "*-1.html"', diff --git a/packages/cli-snapshot/test/file.test.js b/packages/cli-snapshot/test/file.test.js index 123fcc43a..4baa8a196 100644 --- a/packages/cli-snapshot/test/file.test.js +++ b/packages/cli-snapshot/test/file.test.js @@ -66,8 +66,8 @@ describe('percy snapshot ', () => { expect(logger.stdout).toEqual([]); expect(logger.stderr).toEqual([ - '[percy] Error: The base-url must include a protocol and hostname ' + - 'when providing a list of snapshots' + '[percy] Error: The \'--base-url\' flag must include ' + + 'a protocol and hostname when providing a list of snapshots' ]); }); @@ -84,9 +84,13 @@ describe('percy snapshot ', () => { it('errors when a page url is invalid', async () => { await expectAsync(snapshot(['./urls.yml'])).toBeRejected(); - expect(logger.stdout).toEqual([]); + expect(logger.stdout).toEqual([ + '[percy] Percy has started!', + '[percy] Stopping percy...' + ]); expect(logger.stderr).toEqual([ - '[percy] Error: Invalid URL: /' + '[percy] Build not created', + '[percy] Error: Invalid snapshot URL: /' ]); }); @@ -96,8 +100,8 @@ describe('percy snapshot ', () => { expect(logger.stderr).toEqual([]); expect(logger.stdout).toEqual([ '[percy] Percy has started!', - '[percy] Processing 1 snapshot...', '[percy] Snapshot taken: YAML Snapshot', + '[percy] Uploading 1 snapshot...', '[percy] Finalized build #1: https://percy.io/test/test/123' ]); }); @@ -113,8 +117,8 @@ describe('percy snapshot ', () => { expect(logger.stderr).toEqual([]); expect(logger.stdout).toEqual([ '[percy] Percy has started!', - '[percy] Processing 1 snapshot...', '[percy] Snapshot taken: JSON Snapshot', + '[percy] Uploading 1 snapshot...', '[percy] Finalized build #1: https://percy.io/test/test/123' ]); }); @@ -125,10 +129,10 @@ describe('percy snapshot ', () => { expect(logger.stderr).toEqual([]); expect(logger.stdout).toEqual([ '[percy] Percy has started!', - '[percy] Processing 1 snapshot...', '[percy] Snapshot taken: JS Snapshot', '[percy] Snapshot taken: JS Snapshot 2', '[percy] Snapshot taken: Other JS Snapshot', + '[percy] Uploading 3 snapshots...', '[percy] Finalized build #1: https://percy.io/test/test/123' ]); }); @@ -139,8 +143,8 @@ describe('percy snapshot ', () => { expect(logger.stderr).toEqual([]); expect(logger.stdout).toEqual([ '[percy] Percy has started!', - '[percy] Processing 1 snapshot...', '[percy] Snapshot taken: JS Function Snapshot', + '[percy] Uploading 1 snapshot...', '[percy] Finalized build #1: https://percy.io/test/test/123' ]); }); @@ -151,8 +155,8 @@ describe('percy snapshot ', () => { expect(logger.stderr).toEqual([]); expect(logger.stdout).toEqual([ '[percy] Percy has started!', - '[percy] Processing 1 snapshot...', '[percy] Snapshot taken: JS Default Snapshot', + '[percy] Uploading 1 snapshot...', '[percy] Finalized build #1: https://percy.io/test/test/123' ]); }); @@ -163,10 +167,10 @@ describe('percy snapshot ', () => { expect(logger.stderr).toEqual([]); expect(logger.stdout).toEqual(jasmine.arrayContaining([ '[percy] Percy has started!', - '[percy] Processing 3 snapshots...', '[percy] Snapshot taken: /', '[percy] Snapshot taken: /one', '[percy] Snapshot taken: /two', + '[percy] Uploading 3 snapshots...', '[percy] Finalized build #1: https://percy.io/test/test/123' ])); }); @@ -186,12 +190,12 @@ describe('percy snapshot ', () => { expect(logger.stderr).toEqual([]); expect(logger.stdout).toEqual(jasmine.arrayContaining([ '[percy] Percy has started!', - '[percy] Processing 5 snapshots...', '[percy] Snapshot taken: Snapshot #2', '[percy] Snapshot taken: Snapshot #22', '[percy] Snapshot taken: Snapshot #42', '[percy] Snapshot taken: Snapshot #62', '[percy] Snapshot taken: Snapshot #82', + '[percy] Uploading 5 snapshots...', '[percy] Finalized build #1: https://percy.io/test/test/123' ])); }); @@ -234,11 +238,15 @@ describe('percy snapshot ', () => { snapshot(['./invalid.yml', '--dry-run']) ).toBeRejected(); - expect(logger.stdout).toEqual([]); + expect(logger.stdout).toEqual([ + '[percy] Percy has started!', + '[percy] Stopping percy...' + ]); expect(logger.stderr).toEqual([ '[percy] Invalid snapshot options:', '[percy] - snapshots[0].url: missing required property', '[percy] - snapshots[0].foo: unknown property', + '[percy] Build not created', '[percy] Error: No snapshots found' ]); }); diff --git a/packages/cli-snapshot/test/sitemap.test.js b/packages/cli-snapshot/test/sitemap.test.js index 81a2c4fe7..9a7b21756 100644 --- a/packages/cli-snapshot/test/sitemap.test.js +++ b/packages/cli-snapshot/test/sitemap.test.js @@ -68,8 +68,12 @@ describe('percy snapshot ', () => { snapshot(['http://localhost:8000/not-a-sitemap']) ).toBeRejected(); - expect(logger.stdout).toEqual([]); + expect(logger.stdout).toEqual([ + '[percy] Percy has started!', + '[percy] Stopping percy...' + ]); expect(logger.stderr).toEqual([ + '[percy] Build not created', '[percy] Error: The sitemap must be an XML document, ' + 'but the content-type was "text/html"' ]); @@ -79,7 +83,7 @@ describe('percy snapshot ', () => { fs.writeFileSync('.percy.yml', [ 'version: 2', 'sitemap:', - ' overrides:', + ' options:', ' - additionalSnapshots:', ' - suffix: " (2)"', ' - include: "^/$"', diff --git a/packages/cli-snapshot/test/unit/config.test.js b/packages/cli-snapshot/test/unit/config.test.js index d319fe422..785947535 100644 --- a/packages/cli-snapshot/test/unit/config.test.js +++ b/packages/cli-snapshot/test/unit/config.test.js @@ -44,7 +44,9 @@ describe('Unit / Config Migration', () => { expect(mocked.migrate.deprecate).toEqual([ ['static.files', { map: 'static.include', type: 'config', until: '1.0.0' }], - ['static.ignore', { map: 'static.exclude', type: 'config', until: '1.0.0' }] + ['static.ignore', { map: 'static.exclude', type: 'config', until: '1.0.0' }], + ['static.overrides', { map: 'static.options', type: 'config', until: '1.0.0' }], + ['sitemap.overrides', { map: 'sitemap.options', type: 'config', until: '1.0.0' }] ]); }); diff --git a/yarn.lock b/yarn.lock index 4f90ca771..1e851102f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2497,11 +2497,6 @@ byte-size@^7.0.0: resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-7.0.1.tgz#b1daf3386de7ab9d706b941a748dbfc71130dee3" integrity sha512-crQdqyCwhokxwV1UyDzLZanhkugAgft7vt0qbbdt60C6Zf3CAiGmtUCylbtYwrU6loOUw3euGrNtW1J651ot1A== -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= - bytes@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" @@ -2813,11 +2808,6 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= -content-disposition@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" - integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ= - content-disposition@^0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" @@ -3684,13 +3674,6 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= -fast-url-parser@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d" - integrity sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0= - dependencies: - punycode "^1.3.2" - fastq@^1.6.0: version "1.10.1" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.10.1.tgz#8b8f2ac8bf3632d67afcd65dac248d5fdc45385e" @@ -5363,29 +5346,29 @@ micromatch@^4.0.4: braces "^3.0.1" picomatch "^2.2.3" +mime-db@1.45.0: + version "1.45.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea" + integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w== + mime-db@1.52.0: version "1.52.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-db@~1.33.0: - version "1.33.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" - integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ== - -mime-types@2.1.18: - version "2.1.18" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" - integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ== +mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.24: + version "2.1.28" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.28.tgz#1160c4757eab2c5363888e005273ecf79d2a0ecd" + integrity sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ== dependencies: - mime-db "~1.33.0" + mime-db "1.45.0" -mime-types@^2.1.12, mime-types@^2.1.34, mime-types@~2.1.19, mime-types@~2.1.24: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== +mime-types@^2.1.34: + version "2.1.34" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24" + integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A== dependencies: - mime-db "1.52.0" + mime-db "1.51.0" mime@^2.5.2: version "2.5.2" @@ -5402,7 +5385,7 @@ min-indent@^1.0.0: resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== -minimatch@3.0.4, minimatch@^3.0.4, minimatch@~3.0.2: +minimatch@^3.0.4, minimatch@~3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -6148,11 +6131,6 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -path-is-inside@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" - integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= - path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" @@ -6163,11 +6141,6 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-to-regexp@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.2.1.tgz#90b617025a16381a879bc82a38d4e8bdeb2bcf45" - integrity sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ== - path-to-regexp@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.0.tgz#f7b3803336104c346889adece614669230645f38" @@ -6209,7 +6182,7 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3, picomatch@^2.3.0: +picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== @@ -6354,11 +6327,6 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -punycode@^1.3.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= - punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" @@ -6418,11 +6386,6 @@ quick-lru@^4.0.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== -range-parser@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" - integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4= - range-parser@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" @@ -6849,20 +6812,6 @@ semver@^7.0.0, semver@^7.1.1, semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semve dependencies: lru-cache "^6.0.0" -serve-handler@^6.1.3: - version "6.1.3" - resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.3.tgz#1bf8c5ae138712af55c758477533b9117f6435e8" - integrity sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w== - dependencies: - bytes "3.0.0" - content-disposition "0.5.2" - fast-url-parser "1.1.3" - mime-types "2.1.18" - minimatch "3.0.4" - path-is-inside "1.0.2" - path-to-regexp "2.2.1" - range-parser "1.2.0" - set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"