[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 <chris.barber@vercel.com>
Co-authored-by: Chris Barber <chris@cb1inc.com>
Co-authored-by: Sean Massa <EndangeredMassa@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
Steven
2022-12-01 18:07:52 -05:00
committed by GitHub
parent d649a3c931
commit 577fd3e979
8 changed files with 518 additions and 162 deletions

View File

@@ -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",

View File

@@ -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');
}

View File

@@ -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,8 +142,15 @@ const main = async () => {
}
// Print update information, if available
if (notifier.update && notifier.update.latest !== pkg.version && isTTY) {
const { latest } = notifier.update;
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')} ` +
@@ -161,11 +161,12 @@ const main = async () => {
);
console.log(
info(
`${info(
`Changelog: https://github.com/vercel/vercel/releases/tag/vercel@${latest}`
)
)}\n`
);
}
}
// The second argument to the command can be:
//

View File

@@ -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;
}
}

View File

@@ -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();
});
}

View File

@@ -0,0 +1,140 @@
import fs from 'fs-extra';
import sleep from '../../../src/util/sleep';
import tmp from 'tmp-promise';
import getLatestVersion from '../../../src/util/get-latest-version';
import { join } from 'path';
tmp.setGracefulCleanup();
const cacheDir = tmp.tmpNameSync({
prefix: 'test-vercel-cli-get-latest-version-',
});
const cacheFile = join(cacheDir, 'package-updates', 'vercel-latest.json');
const pkg = {
name: 'vercel',
version: '27.3.0',
};
const versionRE = /^\d+\.\d+\.\d+$/;
describe('get latest version', () => {
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;
}
}
}

131
yarn.lock
View File

@@ -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"