From 577fd3e9792683daa8cd6e52786bac2d25d95b56 Mon Sep 17 00:00:00 2001 From: Steven Date: Thu, 1 Dec 2022 18:07:52 -0500 Subject: [PATCH] [cli] Replace `update-notifier` dependency with built in (#8090) This PR replaces the `update-notifier` dependency with a custom implementation. There are a few reasons: the dependency is quite large, it requires ESM in order to update, and can sometimes suggest an update to an older version. For example: ![image](https://user-images.githubusercontent.com/229881/195891579-c8c047a6-51ec-45f2-b597-daf927f48203.png) - Related to #8038 Co-authored-by: Chris Barber Co-authored-by: Chris Barber Co-authored-by: Sean Massa Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- packages/cli/package.json | 3 +- packages/cli/scripts/build.ts | 22 ++- packages/cli/src/index.ts | 47 ++--- .../get-latest-version/get-latest-worker.js | 186 ++++++++++++++++++ .../cli/src/util/get-latest-version/index.ts | 151 ++++++++++++++ .../unit/create-git-meta/not-dirty/git/index | Bin 145 -> 145 bytes .../test/unit/util/get-latest-version.test.ts | 140 +++++++++++++ yarn.lock | 131 +----------- 8 files changed, 518 insertions(+), 162 deletions(-) create mode 100644 packages/cli/src/util/get-latest-version/get-latest-worker.js create mode 100644 packages/cli/src/util/get-latest-version/index.ts create mode 100644 packages/cli/test/unit/util/get-latest-version.test.ts diff --git a/packages/cli/package.json b/packages/cli/package.json index 891042cc57a..bdfec36c51e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -50,8 +50,7 @@ "@vercel/redwood": "1.0.38", "@vercel/remix": "1.1.0", "@vercel/ruby": "1.3.44", - "@vercel/static-build": "1.0.41", - "update-notifier": "5.1.0" + "@vercel/static-build": "1.0.41" }, "devDependencies": { "@alex_neo/jest-expect-message": "1.0.5", diff --git a/packages/cli/scripts/build.ts b/packages/cli/scripts/build.ts index 8163d98da9a..0a5bcb3d27f 100644 --- a/packages/cli/scripts/build.ts +++ b/packages/cli/scripts/build.ts @@ -1,7 +1,7 @@ import cpy from 'cpy'; import execa from 'execa'; import { join } from 'path'; -import { remove, writeFile } from 'fs-extra'; +import { remove, readJSON, writeFile } from 'fs-extra'; const dirRoot = join(__dirname, '..'); const distRoot = join(dirRoot, 'dist'); @@ -43,15 +43,15 @@ async function main() { stdio: 'inherit', }); + const pkg = await readJSON(join(dirRoot, 'package.json')); + const dependencies = Object.keys(pkg?.dependencies ?? {}); // Do the initial `ncc` build - console.log(); - const args = [ - 'ncc', - 'build', - '--external', - 'update-notifier', - 'src/index.ts', - ]; + console.log('Dependencies:', dependencies); + const externs = []; + for (const dep of dependencies) { + externs.push('--external', dep); + } + const args = ['ncc', 'build', 'src/index.ts', ...externs]; await execa('yarn', args, { stdio: 'inherit', cwd: dirRoot }); // `ncc` has some issues with `@vercel/fun`'s runtime files: @@ -78,6 +78,10 @@ async function main() { // Band-aid to bundle stuff that `ncc` neglects to bundle await cpy(join(dirRoot, 'src/util/projects/VERCEL_DIR_README.txt'), distRoot); await cpy(join(dirRoot, 'src/util/dev/builder-worker.js'), distRoot); + await cpy( + join(dirRoot, 'src/util/get-latest-version/get-latest-worker.js'), + distRoot + ); console.log('Finished building Vercel CLI'); } diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index b409b54fa9b..ba68c2ecbdc 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -18,7 +18,7 @@ import sourceMap from '@zeit/source-map-support'; import { mkdirp } from 'fs-extra'; import chalk from 'chalk'; import epipebomb from 'epipebomb'; -import updateNotifier from 'update-notifier'; +import getLatestVersion from './util/get-latest-version'; import { URL } from 'url'; import * as Sentry from '@sentry/node'; import hp from './util/humanize-path'; @@ -55,13 +55,6 @@ import { VercelConfig } from '@vercel/client'; const isCanary = pkg.version.includes('canary'); -// Checks for available update and returns an instance -const notifier = updateNotifier({ - pkg, - distTag: isCanary ? 'canary' : 'latest', - updateCheckInterval: 1000 * 60 * 60 * 24 * 7, // 1 week -}); - const VERCEL_DIR = getGlobalPathConfig(); const VERCEL_CONFIG_PATH = configFiles.getConfigFilePath(); const VERCEL_AUTH_CONFIG_PATH = configFiles.getAuthConfigFilePath(); @@ -149,22 +142,30 @@ const main = async () => { } // Print update information, if available - if (notifier.update && notifier.update.latest !== pkg.version && isTTY) { - const { latest } = notifier.update; - console.log( - info( - `${chalk.black.bgCyan('UPDATE AVAILABLE')} ` + - `Run ${cmd( - await getUpdateCommand() - )} to install ${getTitleName()} CLI ${latest}` - ) - ); + if (isTTY && !process.env.NO_UPDATE_NOTIFIER) { + // Check if an update is available. If so, `latest` will contain a string + // of the latest version, otherwise `undefined`. + const latest = getLatestVersion({ + distTag: isCanary ? 'canary' : 'latest', + output, + pkg, + }); + if (latest) { + console.log( + info( + `${chalk.black.bgCyan('UPDATE AVAILABLE')} ` + + `Run ${cmd( + await getUpdateCommand() + )} to install ${getTitleName()} CLI ${latest}` + ) + ); - console.log( - info( - `Changelog: https://github.com/vercel/vercel/releases/tag/vercel@${latest}` - ) - ); + console.log( + `${info( + `Changelog: https://github.com/vercel/vercel/releases/tag/vercel@${latest}` + )}\n` + ); + } } // The second argument to the command can be: diff --git a/packages/cli/src/util/get-latest-version/get-latest-worker.js b/packages/cli/src/util/get-latest-version/get-latest-worker.js new file mode 100644 index 00000000000..26f1628113b --- /dev/null +++ b/packages/cli/src/util/get-latest-version/get-latest-worker.js @@ -0,0 +1,186 @@ +/** + * This file is spawned in the background and checks npm for the latest version + * of the CLI, then writes the version to the cache file. + * + * NOTE: Since this file runs asynchronously in the background, it's possible + * for multiple instances of this file to be running at the same time leading + * to a race condition where the most recent instance will overwrite the + * previous cache file resetting the `notified` flag and cause the update + * notification to appear for multiple consequetive commands. Not the end of + * the world, but something to be aware of. + */ + +const fetch = require('node-fetch'); +const fs = require('fs-extra'); +const path = require('path'); +const { Agent: HttpsAgent } = require('https'); +const { bold, gray, red } = require('chalk'); +const { format, inspect } = require('util'); + +/** + * An simple output helper which accumulates error and debug log messages in + * memory for potential persistance to disk while immediately outputting errors + * and debug messages, when the `--debug` flag is set, to `stderr`. + */ +class WorkerOutput { + debugLog = []; + logFile = null; + + constructor({ debug = true }) { + this.debugOutputEnabled = debug; + } + + debug(...args) { + this.print('debug', args); + } + + error(...args) { + this.print('error', args); + } + + print(type, args) { + const str = format( + ...args.map(s => (typeof s === 'string' ? s : inspect(s))) + ); + this.debugLog.push(`[${new Date().toISOString()}] [${type}] ${str}`); + if (type === 'debug' && this.debugOutputEnabled) { + console.error( + `${gray('>')} ${bold('[debug]')} ${gray( + `[${new Date().toISOString()}]` + )} ${str}` + ); + } else if (type === 'error') { + console.error(`${red(`Error:`)} ${str}`); + } + } + + setLogFile(file) { + // wire up the exit handler the first time the log file is set + if (this.logFile === null) { + process.on('exit', () => { + if (this.debugLog.length) { + fs.outputFileSync(this.logFile, this.debugLog.join('\n')); + } + }); + } + this.logFile = file; + } +} + +const output = new WorkerOutput({ + // enable the debug logging if the `--debug` is set or if this worker script + // was directly executed + debug: process.argv.includes('--debug') || !process.connected, +}); + +process.on('unhandledRejection', err => { + output.error('Exiting worker due to unhandled rejection:', err); + process.exit(1); +}); + +// this timer will prevent this worker process from running longer than 10s +const timer = setTimeout(() => { + output.error('Worker timed out after 10 seconds'); + process.exit(1); +}, 10000); + +// wait for the parent to give us the work payload +process.once('message', async msg => { + output.debug('Received message from parent:', msg); + + output.debug('Disconnecting from parent'); + process.disconnect(); + + const { cacheFile, distTag, name, updateCheckInterval } = msg; + const cacheFileParsed = path.parse(cacheFile); + await fs.mkdirp(cacheFileParsed.dir); + + output.setLogFile( + path.join(cacheFileParsed.dir, `${cacheFileParsed.name}.log`) + ); + + const lockFile = path.join( + cacheFileParsed.dir, + `${cacheFileParsed.name}.lock` + ); + + try { + // check for a lock file and either bail if running or write our pid and continue + output.debug(`Checking lock file: ${lockFile}`); + if (await isRunning(lockFile)) { + output.debug('Worker already running, exiting'); + process.exit(1); + } + output.debug(`Initializing lock file with pid ${process.pid}`); + await fs.writeFile(lockFile, String(process.pid), 'utf-8'); + + // fetch the latest version from npm + const agent = new HttpsAgent({ + keepAlive: true, + maxSockets: 15, // See: `npm config get maxsockets` + }); + const headers = { + accept: + 'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*', + }; + const url = `https://registry.npmjs.org/${name}`; + output.debug(`Fetching ${url}`); + const res = await fetch(url, { agent, headers }); + const json = await res.json(); + const tags = json['dist-tags']; + const version = tags[distTag]; + + if (version) { + output.debug(`Found dist tag "${distTag}" with version "${version}"`); + } else { + output.error(`Dist tag "${distTag}" not found`); + output.debug('Available dist tags:', Object.keys(tags)); + } + + output.debug(`Writing cache file: ${cacheFile}`); + await fs.outputJSON(cacheFile, { + expireAt: Date.now() + updateCheckInterval, + notified: false, + version, + }); + } catch (err) { + output.error(`Failed to get package info:`, err); + } finally { + clearTimeout(timer); + + output.debug(`Releasing lock file: ${lockFile}`); + await fs.remove(lockFile); + + output.debug(`Worker finished successfully!`); + + // force the worker to exit + process.exit(0); + } +}); + +// signal the parent process we're ready +if (process.connected) { + output.debug("Notifying parent we're ready"); + process.send({ type: 'ready' }); +} else { + console.error('No IPC bridge detected, exiting'); + process.exit(1); +} + +async function isRunning(lockFile) { + try { + const pid = parseInt(await fs.readFile(lockFile, 'utf-8')); + output.debug(`Found lock file with pid: ${pid}`); + + // checks for existence of a process; throws if not found + process.kill(pid, 0); + + // process is still running + return true; + } catch (err) { + // lock file does not exist or process is not running and pid is stale + output.debug(`Resetting lock file: ${err.toString()}`); + await fs.remove(lockFile); + return false; + } +} diff --git a/packages/cli/src/util/get-latest-version/index.ts b/packages/cli/src/util/get-latest-version/index.ts new file mode 100644 index 00000000000..9da8bb5851b --- /dev/null +++ b/packages/cli/src/util/get-latest-version/index.ts @@ -0,0 +1,151 @@ +import semver from 'semver'; +import XDGAppPaths from 'xdg-app-paths'; +import { dirname, parse as parsePath, resolve as resolvePath } from 'path'; +import type { Output } from '../output'; +import { existsSync, outputJSONSync, readJSONSync } from 'fs-extra'; +import type { PackageJson } from '@vercel/build-utils'; +import { spawn } from 'child_process'; + +interface GetLatestVersionOptions { + cacheDir?: string; + distTag?: string; + output?: Output; + pkg: PackageJson; + updateCheckInterval?: number; +} + +interface PackageInfoCache { + version: string; + expireAt: number; + notified: boolean; +} + +interface GetLatestWorkerPayload { + cacheFile?: string; + distTag?: string; + updateCheckInterval?: number; + name?: string; +} + +/** + * Determines if it needs to check for a newer CLI version and returns the last + * detected version. The version could be stale, but still newer than the + * current version. + * + * @returns {String|undefined} If a newer version is found, then the lastest + * version, otherwise `undefined`. + */ +export default function getLatestVersion({ + cacheDir = XDGAppPaths('com.vercel.cli').cache(), + distTag = 'latest', + output, + pkg, + updateCheckInterval = 1000 * 60 * 60 * 24 * 7, // 1 week +}: GetLatestVersionOptions): string | undefined { + if ( + !pkg || + typeof pkg !== 'object' || + !pkg.name || + typeof pkg.name !== 'string' + ) { + throw new TypeError('Expected package to be an object with a package name'); + } + + const cacheFile = resolvePath( + cacheDir, + 'package-updates', + `${pkg.name}-${distTag}.json` + ); + + let cache: PackageInfoCache | undefined; + try { + cache = readJSONSync(cacheFile); + } catch (err: any) { + // cache does not exist or malformed + if (err.code !== 'ENOENT') { + output?.debug(`Error reading latest package cache file: ${err}`); + } + } + + if (!cache || cache.expireAt < Date.now()) { + spawnWorker( + { + cacheFile, + distTag, + updateCheckInterval, + name: pkg.name, + }, + output + ); + } + + if ( + cache && + !cache.notified && + pkg.version && + semver.lt(pkg.version, cache.version) + ) { + cache.notified = true; + outputJSONSync(cacheFile, cache); + return cache.version; + } +} + +/** + * Spawn the worker, wait for the worker to report it's ready, then signal the + * worker to fetch the latest version. + */ +function spawnWorker( + payload: GetLatestWorkerPayload, + output: Output | undefined +) { + // we need to find the update worker script since the location is + // different based on production vs tests + let dir = dirname(__filename); + let script = resolvePath(dir, 'dist', 'get-latest-worker.js'); + const { root } = parsePath(dir); + while (!existsSync(script)) { + dir = dirname(dir); + if (dir === root) { + // didn't find it, bail + output?.debug('Failed to find the get latest worker script!'); + return; + } + script = resolvePath(dir, 'dist', 'get-latest-worker.js'); + } + + // spawn the worker with an IPC channel + output?.debug(`Spawning ${script}`); + const args = [script]; + if (output?.debugEnabled) { + args.push('--debug'); + } + const worker = spawn(process.execPath, args, { + stdio: ['inherit', 'inherit', 'inherit', 'ipc'], + windowsHide: true, + }); + + // we allow the child 2 seconds to let us know it's ready before we give up + const workerReadyTimer = setTimeout(() => worker.kill(), 2000); + + // listen for an early on close error, but then we remove it when unref + const onClose = (code: number) => { + output?.debug(`Get latest worker exited (code ${code})`); + }; + worker.on('close', onClose); + + // generally, the parent won't be around long enough to handle a non-zero + // worker process exit code + worker.on('error', err => { + output?.log(`Failed to spawn get latest worker: ${err.stack}`); + }); + + // wait for the worker to start and notify us it is ready + worker.once('message', () => { + clearTimeout(workerReadyTimer); + + worker.removeListener('close', onClose); + worker.send(payload); + worker.unref(); + }); +} diff --git a/packages/cli/test/fixtures/unit/create-git-meta/not-dirty/git/index b/packages/cli/test/fixtures/unit/create-git-meta/not-dirty/git/index index 9425b8023f3cd66d971d01b67b5f49f5fd35c893..0f234091f49980cb3dfac2194d0f6ac2e6d6691e 100644 GIT binary patch delta 65 zcmbQpIFV7s#WTp6fq{Vuh#8ZWb$+V2C9}e4Mg|5xhQv^hi7JsIS7r%KkE;`uy2>YAoGR8WG { + afterEach(() => fs.remove(cacheDir)); + + it('should find newer version async', async () => { + // 1. first call, no cache file + let latest = getLatestVersion({ + cacheDir, + pkg, + }); + expect(latest).toEqual(undefined); + + await waitForCacheFile(); + + let cache = await fs.readJSON(cacheFile); + expect(typeof cache).toEqual('object'); + expect(typeof cache.expireAt).toEqual('number'); + expect(cache.expireAt).toBeGreaterThan(Date.now()); + expect(typeof cache.version).toEqual('string'); + expect(cache.version).toEqual(expect.stringMatching(versionRE)); + expect(cache.notified).toEqual(false); + + // 2. call again and this time it'll return the version from the cache + latest = getLatestVersion({ + cacheDir, + pkg, + }); + expect(typeof latest).toBe('string'); + expect(latest).toEqual(expect.stringMatching(versionRE)); + + cache = await fs.readJSON(cacheFile); + expect(cache.version).toEqual(expect.stringMatching(versionRE)); + expect(cache.notified).toEqual(true); + + // 3. notification already done, should skip + latest = getLatestVersion({ + cacheDir, + pkg, + }); + expect(latest).toEqual(undefined); + }); + + it('should not find a newer version', async () => { + // 1. first call, no cache file + let latest = getLatestVersion({ + cacheDir, + updateCheckInterval: 1, + pkg: { + ...pkg, + version: '999.0.0', + }, + }); + expect(latest).toEqual(undefined); + + await waitForCacheFile(); + + // 2. call again and should recheck and still not find a new version + latest = getLatestVersion({ + cacheDir, + updateCheckInterval: 1, + pkg: { + ...pkg, + version: '999.0.0', + }, + }); + expect(latest).toEqual(undefined); + }); + + it('should not check twice', async () => { + // 1. first call, no cache file + let latest = getLatestVersion({ + cacheDir, + updateCheckInterval: 1, + pkg, + }); + expect(latest).toEqual(undefined); + + // 2. immediately call again, but should hopefully still be undefined + latest = getLatestVersion({ + cacheDir, + updateCheckInterval: 1, + pkg, + }); + expect(latest).toEqual(undefined); + + await waitForCacheFile(); + + // 3. call again and should recheck and find a new version + latest = getLatestVersion({ + cacheDir, + updateCheckInterval: 1, + pkg, + }); + expect(typeof latest).toBe('string'); + expect(latest).toEqual(expect.stringMatching(versionRE)); + }); + + it('should error if no arguments are passed in', () => { + expect(() => getLatestVersion(undefined as any)).toThrow(TypeError); + }); + + it('should error package is invalid', () => { + expect(() => getLatestVersion({} as any)).toThrow(TypeError); + expect(() => getLatestVersion({ pkg: null as any })).toThrow(TypeError); + expect(() => getLatestVersion({ pkg: {} })).toThrow(TypeError); + expect(() => getLatestVersion({ pkg: { name: null as any } })).toThrow( + TypeError + ); + expect(() => getLatestVersion({ pkg: { name: '' } })).toThrow(TypeError); + }); +}); + +async function waitForCacheFile() { + for (let i = 0; i < 20; i++) { + await sleep(100); + if (await fs.pathExists(cacheFile)) { + return; + } + } +} diff --git a/yarn.lock b/yarn.lock index 665d5087443..012f1db3bf0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4253,20 +4253,6 @@ boxen@^3.0.0: type-fest "^0.3.0" widest-line "^2.0.0" -boxen@^5.0.0: - version "5.1.2" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50" - integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ== - dependencies: - ansi-align "^3.0.0" - camelcase "^6.2.0" - chalk "^4.1.0" - cli-boxes "^2.2.1" - string-width "^4.2.2" - type-fest "^0.20.2" - widest-line "^3.1.0" - wrap-ansi "^7.0.0" - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -4617,14 +4603,6 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.1.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - chance@1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/chance/-/chance-1.1.7.tgz#e99dde5ac16681af787b5ba94c8277c090d6cfe8" @@ -4769,11 +4747,6 @@ cli-boxes@^2.2.0: resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.0.tgz#538ecae8f9c6ca508e3c3c95b453fe93cb4c168d" integrity sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w== -cli-boxes@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" - integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== - cli-cursor@^2.0.0, cli-cursor@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" @@ -5027,18 +5000,6 @@ configstore@^4.0.0: write-file-atomic "^2.0.0" xdg-basedir "^3.0.0" -configstore@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" - integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== - dependencies: - dot-prop "^5.2.0" - graceful-fs "^4.1.2" - make-dir "^3.0.0" - unique-string "^2.0.0" - write-file-atomic "^3.0.0" - xdg-basedir "^4.0.0" - console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" @@ -5280,11 +5241,6 @@ crypto-random-string@^1.0.0: resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4= -crypto-random-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" - integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== - css-select@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b" @@ -5672,7 +5628,7 @@ dot-prop@^4.1.0, dot-prop@^4.2.0: dependencies: is-obj "^1.0.0" -dot-prop@^5.1.0, dot-prop@^5.2.0: +dot-prop@^5.1.0: version "5.2.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" integrity sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A== @@ -6047,11 +6003,6 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== -escape-goat@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" - integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== - escape-html@1.0.3, escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -7136,13 +7087,6 @@ global-dirs@^0.1.0: dependencies: ini "^1.3.4" -global-dirs@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.0.tgz#70a76fe84ea315ab37b1f5576cbde7d48ef72686" - integrity sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA== - dependencies: - ini "2.0.0" - globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -7626,11 +7570,6 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -ini@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" - integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== - ini@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ini/-/ini-3.0.0.tgz#2f6de95006923aa75feed8894f5686165adc08f1" @@ -7901,24 +7840,11 @@ is-installed-globally@^0.1.0: global-dirs "^0.1.0" is-path-inside "^1.0.0" -is-installed-globally@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" - integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== - dependencies: - global-dirs "^3.0.0" - is-path-inside "^3.0.2" - is-npm@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-3.0.0.tgz#ec9147bfb629c43f494cf67936a961edec7e8053" integrity sha512-wsigDr1Kkschp2opC4G3yA6r9EgVA6NjRpWzIi9axXqeIaAATPRJc4uLujXe3Nd9uO8KoDyA4MD6aZSeXTADhA== -is-npm@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8" - integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA== - is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" @@ -7984,11 +7910,6 @@ is-path-inside@^3.0.1: resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017" integrity sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg== -is-path-inside@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== - is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" @@ -9190,7 +9111,7 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -latest-version@^5.0.0, latest-version@^5.1.0: +latest-version@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== @@ -11269,13 +11190,6 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -pupa@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62" - integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== - dependencies: - escape-goat "^2.0.0" - q@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" @@ -11864,13 +11778,6 @@ semver-diff@^2.0.0: dependencies: semver "^5.0.3" -semver-diff@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" - integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== - dependencies: - semver "^6.3.0" - "semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.0.3, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" @@ -12375,7 +12282,7 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" -string-width@^4.2.2, string-width@^4.2.3: +string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -13396,13 +13303,6 @@ unique-string@^1.0.0: dependencies: crypto-random-string "^1.0.0" -unique-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" - integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== - dependencies: - crypto-random-string "^2.0.0" - unique-temp-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unique-temp-dir/-/unique-temp-dir-1.0.0.tgz#6dce95b2681ca003eebfb304a415f9cbabcc5385" @@ -13458,26 +13358,6 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" -update-notifier@5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-5.1.0.tgz#4ab0d7c7f36a231dd7316cf7729313f0214d9ad9" - integrity sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw== - dependencies: - boxen "^5.0.0" - chalk "^4.1.0" - configstore "^5.0.1" - has-yarn "^2.1.0" - import-lazy "^2.1.0" - is-ci "^2.0.0" - is-installed-globally "^0.4.0" - is-npm "^5.0.0" - is-yarn-global "^0.3.0" - latest-version "^5.1.0" - pupa "^2.1.1" - semver "^7.3.4" - semver-diff "^3.1.1" - xdg-basedir "^4.0.0" - update-notifier@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-3.0.1.tgz#78ecb68b915e2fd1be9f767f6e298ce87b736250" @@ -13857,11 +13737,6 @@ xdg-basedir@^3.0.0: resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ= -xdg-basedir@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" - integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== - xdg-portable@^7.0.0: version "7.2.1" resolved "https://registry.yarnpkg.com/xdg-portable/-/xdg-portable-7.2.1.tgz#4301ba0868b2cbc9de0c53b3699906adcc9d2560"