From 545556ee30d468d005a4efea07742e237d44ecd9 Mon Sep 17 00:00:00 2001 From: Cacie Prins Date: Tue, 28 Apr 2026 15:06:42 -0400 Subject: [PATCH] dependency(listr): upgrade listr 3.x to 9.x (#33640) * chore(deps): upgrade listr 3.x to 9.x * use important strings to assert install output instead of fragile snapshots * rm unused `enquirer` dep; write cli test results * revert persisting junit xml for now * ensure VerboseRenderer implements LstrRenderer contract * remove unnecessary vendorized VerboseRenderer; use behavioral assertions in verify instead of brittle snapshots * make most `any` types in cli installer explicit; remove unused deps * changelog * rm trailing slash from pr link * Update cli/lib/tasks/install.ts Co-authored-by: Bill Glesias * simplify task list definitions; improve type annotations * fix version output; improve readability of task generation * rm unused types * revert error handling regression * fix implicit any * more readability improvements; better listr mocking * apply similar readability improvements to verify * fix order of verify -> welcome message * chore: skip adding the install comment on the commit (#33685) * update axios (#33687) --------- Co-authored-by: Bill Glesias Co-authored-by: Matt Schile --- cli/CHANGELOG.md | 1 + cli/lib/VerboseRenderer.ts | 75 ---- cli/lib/tasks/install.ts | 255 ++++++------ cli/lib/tasks/verify.ts | 123 +++--- cli/lib/util.ts | 8 - cli/package.json | 5 +- .../tasks/__snapshots__/install.spec.ts.snap | 282 ------------- .../tasks/__snapshots__/verify.spec.ts.snap | 381 ------------------ cli/test/lib/tasks/install.spec.ts | 174 ++++++-- cli/test/lib/tasks/verify.spec.ts | 211 +++++++--- cli/vitest.config.ts | 1 + package.json | 2 +- scripts/type_check.js | 3 +- yarn.lock | 52 +-- 14 files changed, 468 insertions(+), 1105 deletions(-) delete mode 100644 cli/lib/VerboseRenderer.ts delete mode 100644 cli/test/lib/tasks/__snapshots__/install.spec.ts.snap delete mode 100644 cli/test/lib/tasks/__snapshots__/verify.spec.ts.snap diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 745eccbad8..5528263b30 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -14,6 +14,7 @@ **Dependency Updates:** - Upgraded `cachedir` from `^2.3.0` to `^2.4.0`. Addressed in [#33608](https://github.com/cypress-io/cypress/pull/33608). +- Upgraded `listr2` from `3.8.3` to `^9.0.5`. Addressed in [#33640](https://github.com/cypress-io/cypress/pull/33640). - Upgraded `simple-git` from `3.33.0` to `3.36.0` to address a [Remote Code Execution](https://security.snyk.io/vuln/SNYK-JS-SIMPLEGIT-15456078) vulnerability reported in security scans. Addressed in [#33680](https://github.com/cypress-io/cypress/pull/33680). - Upgraded `ts-loader` from `9.5.2` to `9.5.7`. Addresses [#33648](https://github.com/cypress-io/cypress/issues/33648). Addressed in [#33691](https://github.com/cypress-io/cypress/pull/33691) diff --git a/cli/lib/VerboseRenderer.ts b/cli/lib/VerboseRenderer.ts deleted file mode 100644 index 600706b9fd..0000000000 --- a/cli/lib/VerboseRenderer.ts +++ /dev/null @@ -1,75 +0,0 @@ -// Vendored from @cypress/listr-verbose-renderer -import figures from 'figures' -import cliCursor from 'cli-cursor' -import chalk from 'chalk' -import dayjs from 'dayjs' - -const formattedLog = (options: any, output: string): void => { - const timestamp = dayjs().format(options.dateFormat) - - // eslint-disable-next-line no-console - console.log(`${chalk.dim(`[${timestamp}]`)} ${output}`) -} - -const renderHelper = (task: any, event: any, options: any): void => { - const log = formattedLog.bind(undefined, options) - - if (event.type === 'STATE') { - const message = task.isPending() ? 'started' : task.state - - log(`${task.title} [${message}]`) - - if (task.isSkipped() && task.output) { - log(`${figures.arrowRight} ${task.output}`) - } - } else if (event.type === 'TITLE') { - log(`${task.title} [title changed]`) - } -} - -const render = (tasks: any[], options: any): void => { - for (const task of tasks) { - task.subscribe( - (event: any) => { - if (event.type === 'SUBTASKS') { - render(task.subtasks, options) - - return - } - - renderHelper(task, event, options) - }, - (err: any) => { - // eslint-disable-next-line no-console - console.log(err) - }, - ) - } -} - -class VerboseRenderer { - private _tasks: any[] - private _options: any - - constructor (tasks: any[], options: any) { - this._tasks = tasks - this._options = Object.assign({ - dateFormat: 'HH:mm:ss', - }, options) - } - - static get nonTTY (): boolean { - return true - } - - render (): void { - cliCursor.hide() - render(this._tasks, this._options) - } - - end (): void { - cliCursor.show() - } -} - -export default VerboseRenderer diff --git a/cli/lib/tasks/install.ts b/cli/lib/tasks/install.ts index b9ecf3b9fe..0ab9f90545 100644 --- a/cli/lib/tasks/install.ts +++ b/cli/lib/tasks/install.ts @@ -4,6 +4,7 @@ import path from 'path' import chalk from 'chalk' import Debug from 'debug' import { Listr } from 'listr2' +import type { ListrTask, ListrContext } from 'listr2' import logSymbols from 'log-symbols' import { stripIndent } from 'common-tags' import timers from 'timers/promises' @@ -16,11 +17,23 @@ import state from './state' import unzip from './unzip' import logger from '../logger' import { throwFormErrorText, errors } from '../errors' -import verbose from '../VerboseRenderer' import { relativeToRepoRoot } from '../relative-to-repo-root' -const debug = Debug('cypress:cli') +const debug = Debug('cypress:cli:install') -function _getBinaryUrlFromBuildInfo (version: string, arch: string, { commitSha, commitBranch }: any): string { +interface CypressBuildInfo { + commitSha: string + commitBranch: string + commitDate: string + stable: boolean +} + +interface BuildPlatform { + arch: string + envVarVersion?: string + buildInfo?: CypressBuildInfo +} + +function _getBinaryUrlFromBuildInfo (version: string, arch: string, { commitSha, commitBranch }: { commitSha: string, commitBranch: string }): string { const platform = os.platform() if ((platform === 'win32') && (arch === 'arm64')) { @@ -76,71 +89,6 @@ const displayCompletionMsg = (): void => { logger.log() } -const downloadAndUnzip = ({ version, installDir, downloadDir }: any): any => { - const progress = { - throttle: 100, - onProgress: null, - } - const downloadDestination = path.join(downloadDir, `cypress-${process.pid}.zip`) - const rendererOptions = getRendererOptions() - - // let the user know what version of cypress we're downloading! - logger.log(`Installing Cypress ${chalk.gray(`(version: ${version})`)}`) - logger.log() - - const tasks = new Listr([ - { - options: { title: util.titleize('Downloading Cypress') }, - task: async (ctx: any, task: any) => { - // as our download progresses indicate the status - progress.onProgress = progessify(task, 'Downloading Cypress') - - const redirectVersion = await download.start({ version, downloadDestination, progress }) - - if (redirectVersion) version = redirectVersion - - debug(`finished downloading file: ${downloadDestination}`) - - // save the download destination for unzipping - util.setTaskTitle( - task, - util.titleize(chalk.green('Downloaded Cypress')), - rendererOptions.renderer, - ) - }, - }, - unzipTask({ - progress, - zipFilePath: downloadDestination, - installDir, - rendererOptions, - }), - { - options: { title: util.titleize('Finishing Installation') }, - task: async (ctx: any, task: any) => { - const cleanup = async () => { - debug('removing zip file %s', downloadDestination) - - await fs.remove(downloadDestination) - } - - await cleanup() - - debug('finished installation in', installDir) - - util.setTaskTitle( - task, - util.titleize(chalk.green('Finished Installation'), chalk.gray(installDir)), - rendererOptions.renderer, - ) - }, - }, - ], { rendererOptions }) - - // start the tasks! - return tasks.run() -} - const validateOS = async (): Promise => { const platformInfo = await util.getPlatformInfo() @@ -151,7 +99,7 @@ const validateOS = async (): Promise => { * Returns the version to install - either a string like `1.2.3` to be fetched * from the download server or a file path or HTTP URL. */ -function getVersionOverride (version: string, { arch, envVarVersion, buildInfo }: any): string | undefined { +function getVersionOverride (version: string, { arch, envVarVersion, buildInfo }: BuildPlatform): string | undefined { // let this environment variable reset the binary version we need if (envVarVersion) { return envVarVersion @@ -190,7 +138,12 @@ function getEnvVarVersion (): string | undefined { return envVarVersion } -const start = async (options: any = {}): Promise => { +interface StartOptions { + force?: boolean + buildInfo?: CypressBuildInfo +} + +const start = async (options: StartOptions = {}): Promise => { debug('installing with options %j', options) const envVarVersion = getEnvVarVersion() @@ -251,16 +204,16 @@ const start = async (options: any = {}): Promise => { try { await fs.ensureDir(cacheDir) - } catch (err: any) { - if (err.code === 'EACCES') { - return throwFormErrorText(errors.invalidCacheDirectory)(stripIndent` - Failed to access ${chalk.cyan(cacheDir)}: + } catch (err: unknown) { + if (err instanceof Error && 'code' in err && err.code === 'EACCES') { + return throwFormErrorText(errors.invalidCacheDirectory)(stripIndent` + Failed to access ${chalk.cyan(cacheDir)}: - ${err.message} - `) + ${err.message} + `) + } else { + throw err } - - throw err } const binaryPkg = await state.getBinaryPkgAsync(binaryDir) @@ -338,35 +291,27 @@ const start = async (options: any = {}): Promise => { const pathToLocalFile = await getLocalFilePath() - if (pathToLocalFile) { - const absolutePath = path.resolve(versionToInstall) - - debug('found local file at', absolutePath) - debug('skipping download') - - const rendererOptions = getRendererOptions() - - return new Listr([unzipTask({ - progress: { - throttle: 100, - onProgress: null, - }, - zipFilePath: absolutePath, - installDir, - rendererOptions, - })], { rendererOptions }).run() - } + const tasks = pathToLocalFile ? + installFromLocal(pathToLocalFile, installDir) : + installFromRemote(versionToInstall, installDir) if (options.force) { debug('Cypress already installed at', installDir) debug('but the installation was forced') } - debug('preparing to download and unzip version ', versionToInstall, 'to path', installDir) + // let the user know what version of cypress we're downloading! + logger.log(`Installing Cypress ${chalk.gray(`(version: ${versionToInstall})`)}`) + logger.log() - const downloadDir = os.tmpdir() + const taskRunner = new Listr( + tasks, + { + silentRendererCondition: () => logger.logLevel() === 'silent', + }, + ) - await downloadAndUnzip({ version: versionToInstall, installDir, downloadDir }) + await taskRunner.run() // delay 1 sec for UX, unless we are testing await timers.setTimeout(1000) @@ -374,54 +319,98 @@ const start = async (options: any = {}): Promise => { displayCompletionMsg() } -const unzipTask = ({ zipFilePath, installDir, progress, rendererOptions }: any): any => { - return { - options: { title: util.titleize('Unzipping Cypress') }, - task: async (ctx: any, task: any) => { - // as our unzip progresses indicate the status - progress.onProgress = progessify(task, 'Unzipping Cypress') +function downloadArchive (version: string, downloadDestination: string): ListrTask { + const inProgressTitle = 'Downloading Cypress' + const completedTitle = chalk.green('Downloaded Cypress') - await unzip.start({ zipFilePath, installDir, progress }) - util.setTaskTitle( - task, - util.titleize(chalk.green('Unzipped Cypress')), - rendererOptions.renderer, - ) + return { + title: util.titleize(inProgressTitle), + task: async (ctx, task) => { + await download.start({ + version, + downloadDestination, + progress: { + throttle: 100, + onProgress: (percentComplete: number, remaining: number) => { + task.title = progressTitle(inProgressTitle, percentComplete, remaining) + }, + }, + }) + + debug(`finished downloading file: ${downloadDestination}`) + + task.title = util.titleize(completedTitle) }, } } -const progessify = (task: any, title: string): any => { - // return higher order function - return (percentComplete: number, remaining: number) => { - const percentCompleteStr = chalk.white(` ${percentComplete}%`) +function installFromLocal (pathToLocalFile: string, installDir: string): ListrTask[] { + const zipFilePath = path.resolve(pathToLocalFile) - // pluralize seconds remaining - const remainingStr = chalk.gray(`${remaining}s`) + debug('found local file at', zipFilePath) + debug('skipping download') - util.setTaskTitle( - task, - util.titleize(title, percentCompleteStr, remainingStr), - getRendererOptions().renderer, - ) + return [ + unzipArchive(zipFilePath, installDir), + ] +} + +function installFromRemote (version: string, installDir: string): ListrTask[] { + const downloadDestination = path.join(os.tmpdir(), `cypress-${process.pid}.zip`) + + debug('preparing to download and unzip version ', version, 'to path', installDir) + + return [ + downloadArchive(version, downloadDestination), + unzipArchive(downloadDestination, installDir), + cleanup(downloadDestination, installDir), + ] +} + +function unzipArchive (zipFilePath: string, installDir: string): ListrTask { + const inProgressTitle = 'Unzipping Cypress' + const completedTitle = chalk.green('Unzipped Cypress') + + return { + title: util.titleize(inProgressTitle), + task: async (ctx, task) => { + await unzip.start({ + zipFilePath, + installDir, + progress: { + onProgress: (percentComplete: number, remaining: number) => { + task.title = progressTitle(inProgressTitle, percentComplete, remaining) + }, + }, + }) + + task.title = util.titleize(completedTitle) + }, } } -// if we are running in CI then use -// the verbose renderer else use -// the default -const getRendererOptions = (): any => { - let renderer = util.isCi() ? verbose : 'default' - - if (logger.logLevel() === 'silent') { - renderer = 'silent' - } - +function cleanup (archiveLocation: string, installDir: string): ListrTask { return { - renderer, + title: util.titleize('Finishing Installation'), + task: async (ctx, task) => { + debug('removing zip file %s', archiveLocation) + + await fs.remove(archiveLocation) + + debug('finished installation in', installDir) + + task.title = util.titleize(chalk.green('Finished Installation'), chalk.gray(installDir)) + }, } } +function progressTitle (title: string, percentComplete: number, remaining: number): string { + return util.titleize(title, + chalk.white(` ${percentComplete}%`), + chalk.gray(`${remaining}s`), + ) +} + export default { start, _getBinaryUrlFromBuildInfo, diff --git a/cli/lib/tasks/verify.ts b/cli/lib/tasks/verify.ts index 9710f26ca4..e06374e58a 100644 --- a/cli/lib/tasks/verify.ts +++ b/cli/lib/tasks/verify.ts @@ -6,7 +6,6 @@ import { stripIndent } from 'common-tags' import Bluebird from 'bluebird' import logSymbols from 'log-symbols' import os from 'os' -import verbose from '../VerboseRenderer' import { throwFormErrorText, errors } from '../errors' import util from '../util' import logger from '../logger' @@ -181,87 +180,67 @@ const runSmokeTest = (binaryDir: string, options: any): any => { return userFriendlySpawn(linuxWithDisplayEnv) } -function testBinary (version: string, binaryDir: string, options: any): Promise { - debug('running binary verification check', version) +function logVersionMismatch (binaryVersion: string, binaryDir: string, packageVersion: string): void { + logger.log(`Found binary version ${chalk.green(binaryVersion)} installed in: ${chalk.cyan(binaryDir)}`) + logger.log() + logger.warn(stripIndent` + + + ${logSymbols.warning} Warning: Binary version ${chalk.green(binaryVersion)} does not match the expected package version ${chalk.green(packageVersion)} + + These versions may not work properly together. + `) + + logger.log() +} + +async function verifyBinary (installedVersion: string, binaryDir: string, options: any): Promise { + debug('running binary verification check', installedVersion) // if running from 'cypress verify', don't print this message if (!options.force) { logger.log(stripIndent` - It looks like this is your first time using Cypress: ${chalk.cyan(version)} + It looks like this is your first time using Cypress: ${chalk.cyan(installedVersion)} `) } logger.log() - // if we are running in CI then use - // the verbose renderer else use - // the default - let renderer = util.isCi() ? verbose : 'default' + const verifyTaskRunner = new Listr([{ + title: util.titleize('Verifying Cypress can run', chalk.gray(binaryDir)), + task: async (ctx, task) => { + debug('clearing out the verified version') - // NOTE: under test we set the listr renderer to 'silent' in order to get deterministic snapshots - if (logger.logLevel() === 'silent' || options.listrRenderer) renderer = 'silent' + await state.clearBinaryStateAsync(binaryDir) - const rendererOptions = { - renderer, - } + await Promise.all([ + runSmokeTest(binaryDir, options), + Bluebird.delay(1500), // good user experience + ]) - const tasks = new Listr([ - { - title: util.titleize('Verifying Cypress can run', chalk.gray(binaryDir)), - task: async (ctx: any, task: any) => { - debug('clearing out the verified version') + debug('write verified: true') - await state.clearBinaryStateAsync(binaryDir) + await state.writeBinaryVerifiedAsync(true, binaryDir) - await Promise.all([ - runSmokeTest(binaryDir, options), - Bluebird.delay(1500), // good user experience - ]) - - debug('write verified: true') - - await state.writeBinaryVerifiedAsync(true, binaryDir) - - util.setTaskTitle( - task, - util.titleize( - chalk.green('Verified Cypress!'), - chalk.gray(binaryDir), - ), - rendererOptions.renderer as string, - ) - }, + task.title = util.titleize( + chalk.green('Verified Cypress!'), + chalk.gray(binaryDir), + ) }, - ] as any, rendererOptions as any) + }], { + silentRendererCondition: () => logger.logLevel() === 'silent', + }) - return tasks.run() -} + await verifyTaskRunner.run() -const maybeVerify = async (installedVersion: string, binaryDir: string, options: any): Promise => { - const isVerified = await state.getBinaryVerifiedAsync(binaryDir) - - debug('is Verified ?', isVerified) - - let shouldVerify = !isVerified - - // force verify if options.force - if (options.force) { - debug('force verify') - shouldVerify = true - } - - if (shouldVerify) { - await testBinary(installedVersion, binaryDir, options) - - if (options.welcomeMessage) { - logger.log() - logger.log('Opening Cypress...') - } + if (options.welcomeMessage) { + logger.log() + logger.log('Opening Cypress...') } } export const start = async (options: any = {}): Promise => { - debug('verifying Cypress app') + debug('verifying Cypress app with options %j', options) _.defaults(options, { dev: false, @@ -351,20 +330,18 @@ export const start = async (options: any = {}): Promise => { if (binaryVersion !== packageVersion) { // warn if we installed with CYPRESS_INSTALL_BINARY or changed version // in the package.json - logger.log(`Found binary version ${chalk.green(binaryVersion)} installed in: ${chalk.cyan(binaryDir)}`) - logger.log() - logger.warn(stripIndent` - - - ${logSymbols.warning} Warning: Binary version ${chalk.green(binaryVersion)} does not match the expected package version ${chalk.green(packageVersion)} - - These versions may not work properly together. - `) - - logger.log() + logVersionMismatch(binaryVersion, binaryDir, packageVersion) } - await maybeVerify(binaryVersion, binaryDir, options) + const isVerified = options.force ? + false : + await state.getBinaryVerifiedAsync(binaryDir) + + debug('is Verified ?', isVerified) + + if (!isVerified) { + await verifyBinary(binaryVersion, binaryDir, options) + } } catch (err: any) { if (err.known) { throw err diff --git a/cli/lib/util.ts b/cli/lib/util.ts index 36a2e65129..d982680da6 100644 --- a/cli/lib/util.ts +++ b/cli/lib/util.ts @@ -21,7 +21,6 @@ import isInstalledGlobally from 'is-installed-globally' import logger from './logger' import Debug from 'debug' import fs from 'fs-extra' -import { readFile } from 'fs/promises' import { relativeToRepoRoot } from './relative-to-repo-root' const debug = Debug('cypress:cli') @@ -403,13 +402,6 @@ const util = { return (_.isFinite(eta) ? (eta / 1000) : 0).toFixed(0) }, - setTaskTitle (task: any, title: string, renderer: string): void { - // only update the renderer title when not running in CI - if (renderer === 'default' && task.title !== title) { - task.title = title - } - }, - isInstalledGlobally (): boolean { return isInstalledGlobally }, diff --git a/cli/package.json b/cli/package.json index 0730d37875..8090efd4b5 100644 --- a/cli/package.json +++ b/cli/package.json @@ -36,22 +36,19 @@ "cachedir": "^2.4.0", "chalk": "^4.1.0", "ci-info": "^4.1.0", - "cli-cursor": "^3.1.0", "cli-table3": "0.6.1", "commander": "^6.2.1", "common-tags": "^1.8.0", "dayjs": "^1.10.4", "debug": "^4.3.4", - "enquirer": "^2.3.6", "eventemitter2": "6.4.7", "execa": "4.1.0", "executable": "^4.1.1", "extract-zip": "2.0.1", - "figures": "^3.2.0", "fs-extra": "^9.1.0", "hasha": "5.2.2", "is-installed-globally": "~0.4.0", - "listr2": "^3.8.3", + "listr2": "^9.0.5", "lodash": "^4.17.23", "log-symbols": "^4.0.0", "minimist": "^1.2.8", diff --git a/cli/test/lib/tasks/__snapshots__/install.spec.ts.snap b/cli/test/lib/tasks/__snapshots__/install.spec.ts.snap deleted file mode 100644 index 164acb9258..0000000000 --- a/cli/test/lib/tasks/__snapshots__/install.spec.ts.snap +++ /dev/null @@ -1,282 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`/lib/tasks/install > .start > exits with error when installing on unsupported os > error when installing on unsupported os 1`] = ` -"Error: The Cypress App could not be installed. Your machine does not meet the operating system requirements. - -https://on.cypress.io/app/get-started/install-cypress#System-requirements - ----------- - -Platform: win32-ia32 -" -`; - -exports[`/lib/tasks/install > .start > is silent when log level is silent > silent install 1 1`] = ` -"[STARTED] Task without title. -[SUCCESS] Task without title. -[STARTED] Task without title. -[SUCCESS] Task without title. -[STARTED] Task without title. -[SUCCESS] Task without title. -" -`; - -exports[`/lib/tasks/install > .start > non-stable builds > logs a warning about installing a pre-release > pre-release warning 1`] = ` -"⚠ Warning: You are installing a pre-release build of Cypress. - -Bugs may be present which do not exist in production builds. - -This build was created from: - * Commit SHA: 3b7f0b5c59def1e9b5f385bd585c9b2836706c29 - * Commit Branch: aBranchName - * Commit Timestamp: 1996-11-27T00:00:00.000Z - -Installing Cypress (version: https://cdn.cypress.io/beta/binary/0.0.0-development/darwin-x64/aBranchName-3b7f0b5c59def1e9b5f385bd585c9b2836706c29/cypress.zip) - -[STARTED] Task without title. -[TITLE]  Downloaded Cypress -[SUCCESS]  Downloaded Cypress -[STARTED] Task without title. -[TITLE]  Unzipped Cypress -[SUCCESS]  Unzipped Cypress -[STARTED] Task without title. -[TITLE]  Finished Installation /cache/Cypress/1.2.3 -[SUCCESS]  Finished Installation /cache/Cypress/1.2.3 - -You can now open Cypress by running one of the following, depending on your package manager: - -- npx cypress open -- yarn cypress open -- pnpm cypress open - -https://on.cypress.io/opening-the-app - -" -`; - -exports[`/lib/tasks/install > .start > override version > as a global install > logs global warning and download > warning installing as global 1 1`] = ` -" -Cypress x.x.x is installed in /cache/Cypress/1.2.3 - -Installing Cypress (version: 1.2.3) - -[STARTED] Task without title. -[TITLE]  Downloaded Cypress -[SUCCESS]  Downloaded Cypress -[STARTED] Task without title. -[TITLE]  Unzipped Cypress -[SUCCESS]  Unzipped Cypress -[STARTED] Task without title. -[TITLE]  Finished Installation /cache/Cypress/1.2.3 -[SUCCESS]  Finished Installation /cache/Cypress/1.2.3 - -⚠ Warning: It looks like you've installed Cypress globally. - - The recommended way to install Cypress is as a devDependency per project. - - You should probably run these commands: - - - npm uninstall -g cypress - - npm install --save-dev cypress -" -`; - -exports[`/lib/tasks/install > .start > override version > failed write access to cache directory > logs error on failure > invalid cache directory 1 1`] = ` -"Error: Cypress cannot write to the cache directory due to file permissions - -See discussion and possible solutions at -https://github.com/cypress-io/cypress/issues/1281 - ----------- - -Failed to access /invalid/cache/dir: - -EACCES: permission denied, mkdir '/invalid' - ----------- - -Platform: darwin-x64 (Foo - OsVersion) -Cypress Version: 1.2.3 -" -`; - -exports[`/lib/tasks/install > .start > override version > warns when specifying cypress version in env > specify version in env vars 1 1`] = ` -"⚠ Warning: Forcing a binary version different than the default. - - The CLI expected to install version: 1.2.3 - - Instead we will install version: 0.12.1 - - These versions may not work properly together. - -Installing Cypress (version: 0.12.1) - -[STARTED] Task without title. -[TITLE]  Downloaded Cypress -[SUCCESS]  Downloaded Cypress -[STARTED] Task without title. -[TITLE]  Unzipped Cypress -[SUCCESS]  Unzipped Cypress -[STARTED] Task without title. -[TITLE]  Finished Installation /cache/Cypress/1.2.3 -[SUCCESS]  Finished Installation /cache/Cypress/1.2.3 - -You can now open Cypress by running one of the following, depending on your package manager: - -- npx cypress open -- yarn cypress open -- pnpm cypress open - -https://on.cypress.io/opening-the-app - -" -`; - -exports[`/lib/tasks/install > .start > override version > when getting installed version does not match needed version > logs message and starts download > installed version does not match needed version 1 1`] = ` -" -Cypress x.x.x is installed in /cache/Cypress/1.2.3 - -Installing Cypress (version: 1.2.3) - -[STARTED] Task without title. -[TITLE]  Downloaded Cypress -[SUCCESS]  Downloaded Cypress -[STARTED] Task without title. -[TITLE]  Unzipped Cypress -[SUCCESS]  Unzipped Cypress -[STARTED] Task without title. -[TITLE]  Finished Installation /cache/Cypress/1.2.3 -[SUCCESS]  Finished Installation /cache/Cypress/1.2.3 - -You can now open Cypress by running one of the following, depending on your package manager: - -- npx cypress open -- yarn cypress open -- pnpm cypress open - -https://on.cypress.io/opening-the-app - -" -`; - -exports[`/lib/tasks/install > .start > override version > when getting installed version fails > logs message and starts download > continues installing on failure 1 1`] = ` -"Installing Cypress (version: 1.2.3) - -[STARTED] Task without title. -[TITLE]  Downloaded Cypress -[SUCCESS]  Downloaded Cypress -[STARTED] Task without title. -[TITLE]  Unzipped Cypress -[SUCCESS]  Unzipped Cypress -[STARTED] Task without title. -[TITLE]  Finished Installation /cache/Cypress/1.2.3 -[SUCCESS]  Finished Installation /cache/Cypress/1.2.3 - -You can now open Cypress by running one of the following, depending on your package manager: - -- npx cypress open -- yarn cypress open -- pnpm cypress open - -https://on.cypress.io/opening-the-app - -" -`; - -exports[`/lib/tasks/install > .start > override version > when running in CI > uses verbose renderer > installing in ci 1 1`] = ` -" -Cypress x.x.x is installed in /cache/Cypress/1.2.3 - -Installing Cypress (version: 1.2.3) - -[STARTED] Task without title. -[SUCCESS] Task without title. -[STARTED] Task without title. -[SUCCESS] Task without title. -[STARTED] Task without title. -[SUCCESS] Task without title. - -You can now open Cypress by running one of the following, depending on your package manager: - -- npx cypress open -- yarn cypress open -- pnpm cypress open - -https://on.cypress.io/opening-the-app - -" -`; - -exports[`/lib/tasks/install > .start > override version > when there is no install version > logs message and starts download > installs without existing installation 1 1`] = ` -"Installing Cypress (version: 1.2.3) - -[STARTED] Task without title. -[TITLE]  Downloaded Cypress -[SUCCESS]  Downloaded Cypress -[STARTED] Task without title. -[TITLE]  Unzipped Cypress -[SUCCESS]  Unzipped Cypress -[STARTED] Task without title. -[TITLE]  Finished Installation /cache/Cypress/1.2.3 -[SUCCESS]  Finished Installation /cache/Cypress/1.2.3 - -You can now open Cypress by running one of the following, depending on your package manager: - -- npx cypress open -- yarn cypress open -- pnpm cypress open - -https://on.cypress.io/opening-the-app - -" -`; - -exports[`/lib/tasks/install > .start > override version > when version is already installed > logs 'skipping install' when explicit cypress install > version already installed - cypress install 1 1`] = ` -" -Cypress 1.2.3 is installed in /cache/Cypress/1.2.3 - -Skipping installation: - - Pass the --force option if you'd like to reinstall anyway. -" -`; - -exports[`/lib/tasks/install > .start > override version > when version is already installed > logs when already installed when run from postInstall > version already installed - postInstall 1 1`] = ` -" -Cypress 1.2.3 is installed in /cache/Cypress/1.2.3 - -" -`; - -exports[`/lib/tasks/install > .start > override version > with force: true > logs message and starts download > forcing true always installs 1 1`] = ` -" -Cypress 1.2.3 is installed in /cache/Cypress/1.2.3 - -Installing Cypress (version: 1.2.3) - -[STARTED] Task without title. -[TITLE]  Downloaded Cypress -[SUCCESS]  Downloaded Cypress -[STARTED] Task without title. -[TITLE]  Unzipped Cypress -[SUCCESS]  Unzipped Cypress -[STARTED] Task without title. -[TITLE]  Finished Installation /cache/Cypress/1.2.3 -[SUCCESS]  Finished Installation /cache/Cypress/1.2.3 - -You can now open Cypress by running one of the following, depending on your package manager: - -- npx cypress open -- yarn cypress open -- pnpm cypress open - -https://on.cypress.io/opening-the-app - -" -`; - -exports[`/lib/tasks/install > .start > skips install > when environment variable is set > skip installation 1 1`] = ` -"Note: Skipping binary installation: Environment variable CYPRESS_INSTALL_BINARY = 0. - -" -`; diff --git a/cli/test/lib/tasks/__snapshots__/verify.spec.ts.snap b/cli/test/lib/tasks/__snapshots__/verify.spec.ts.snap deleted file mode 100644 index 1c66ec05b2..0000000000 --- a/cli/test/lib/tasks/__snapshots__/verify.spec.ts.snap +++ /dev/null @@ -1,381 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`lib/tasks/verify > logs error and exits when executable cannot be found 1`] = ` -"Error: No version of Cypress is installed in: /cache/Cypress/1.2.3/Cypress.app - -Please reinstall Cypress by running: cypress install - ----------- - -Cypress executable not found at: /cache/Cypress/1.2.3/Cypress.app/Contents/MacOS/Cypress - ----------- - -Platform: darwin-x64 (undefined) -Cypress Version: 1.2.3 -" -`; - -exports[`lib/tasks/verify > logs error and exits when no version of Cypress is installed 1`] = ` -"Error: No version of Cypress is installed in: /cache/Cypress/1.2.3/Cypress.app - -Please reinstall Cypress by running: cypress install - ----------- - -Cypress executable not found at: /cache/Cypress/1.2.3/Cypress.app/Contents/MacOS/Cypress - ----------- - -Platform: darwin-x64 (undefined) -Cypress Version: 1.2.3 -" -`; - -exports[`lib/tasks/verify > logs error when child process hangs 1`] = ` -"It looks like this is your first time using Cypress: 1.2.3 - -Error: Cypress verification timed out. - -This command failed with the following output: - -/cache/Cypress/1.2.3/Cypress.app/Contents/MacOS/Cypress --no-sandbox --smoke-test --ping=222 - ----------- - -some stderr - ----------- - -Platform: darwin-x64 (undefined) -Cypress Version: 1.2.3 -" -`; - -exports[`lib/tasks/verify > logs error when child process returns incorrect stdout (stderr when exists) 1`] = ` -"It looks like this is your first time using Cypress: 1.2.3 - -Error: Cypress failed to start. - -This may be due to a missing library or dependency. https://on.cypress.io/required-dependencies - -Please refer to the error below for more details. - ----------- - -some stderr - ----------- - -Platform: darwin-x64 (undefined) -Cypress Version: 1.2.3 -" -`; - -exports[`lib/tasks/verify > logs error when child process returns incorrect stdout (stdout when no stderr) 1`] = ` -"It looks like this is your first time using Cypress: 1.2.3 - -Error: Cypress failed to start. - -This may be due to a missing library or dependency. https://on.cypress.io/required-dependencies - -Please refer to the error below for more details. - ----------- - -Platform: darwin-x64 (undefined) -Cypress Version: 1.2.3 -" -`; - -exports[`lib/tasks/verify > logs warning when installed version does not match verified version 1`] = ` -"Found binary version bloop installed in: /cache/Cypress/1.2.3/Cypress.app - -⚠ Warning: Binary version bloop does not match the expected package version 1.2.3 - - These versions may not work properly together. - -" -`; - -exports[`lib/tasks/verify > on linux > logs error and exits when starting xvfb fails > xvfb fails 1`] = ` -"It looks like this is your first time using Cypress: 1.2.3 - -Error: Xvfb exited with a non zero exit code. - -There was a problem spawning Xvfb. - -This is likely a problem with your system, permissions, or installation of Xvfb. - ----------- - -Error: test without xvfb - ----------- - -Platform: darwin-x64 (undefined) -Cypress Version: 1.2.3 -" -`; - -exports[`lib/tasks/verify > smoke test retries on bad display with our Xvfb > fails on both retries with our Xvfb on Linux > tried to verify twice, on the first try got the DISPLAY error 1`] = ` -"Cypress verification failed. - -Cypress failed to start after spawning a new Xvfb server. - -The error logs we received were: - ----------- - -[some noise here] Gtk: cannot open display: 987 -some other error -again with -some weird indent - ----------- - -This may be due to a missing library or dependency. https://on.cypress.io/required-dependencies - -Please refer to the error above for more detail. - ----------- - -Platform: linux-x64 (undefined) -Cypress Version: 1.2.3" -`; - -exports[`lib/tasks/verify > smoke test retries on bad display with our Xvfb > is silent when logLevel is silent > silent verify 1`] = `""`; - -exports[`lib/tasks/verify > smoke test retries on bad display with our Xvfb > logs an error if Cypress executable does not exist > no Cypress executable 1`] = ` -"Error: No version of Cypress is installed in: /cache/Cypress/1.2.3/Cypress.app - -Please reinstall Cypress by running: cypress install - ----------- - -Cypress executable not found at: /cache/Cypress/1.2.3/Cypress.app/Contents/MacOS/Cypress - ----------- - -Platform: darwin-x64 (undefined) -Cypress Version: 1.2.3 -" -`; - -exports[`lib/tasks/verify > smoke test retries on bad display with our Xvfb > logs an error if Cypress executable does not have permissions > Cypress non-executable permission 1`] = ` -"Error: Cypress cannot run because this binary file does not have executable permissions here: - -/cache/Cypress/1.2.3/Cypress.app/Contents/MacOS/Cypress - -Reasons this may happen: - -- node was installed as 'root' or with 'sudo' -- the cypress npm package as 'root' or with 'sudo' - -Please check that you have the appropriate user permissions. - -You can also try clearing the cache with 'cypress cache clear' and reinstalling. - ----------- - -Platform: darwin-x64 (undefined) -Cypress Version: 1.2.3 -" -`; - -exports[`lib/tasks/verify > smoke test retries on bad display with our Xvfb > logs and runs when current version has not been verified > current version has not been verified 1`] = ` -"It looks like this is your first time using Cypress: 1.2.3 - - -Opening Cypress... -" -`; - -exports[`lib/tasks/verify > smoke test retries on bad display with our Xvfb > logs and runs when installed version is different than package version > different version installed 1`] = ` -"Found binary version 7.8.9 installed in: /cache/Cypress/1.2.3/Cypress.app - -⚠ Warning: Binary version 7.8.9 does not match the expected package version 1.2.3 - - These versions may not work properly together. - -It looks like this is your first time using Cypress: 7.8.9 - - -Opening Cypress... -" -`; - -exports[`lib/tasks/verify > smoke test retries on bad display with our Xvfb > logs error when fails smoke test unexpectedly without stderr > fails with no stderr 1`] = ` -"It looks like this is your first time using Cypress: 1.2.3 - -Error: Cypress failed to start. - -This may be due to a missing library or dependency. https://on.cypress.io/required-dependencies - -Please refer to the error below for more details. - ----------- - -Error: EPERM NOT PERMITTED - ----------- - -Platform: darwin-x64 (undefined) -Cypress Version: 1.2.3 -" -`; - -exports[`lib/tasks/verify > smoke test retries on bad display with our Xvfb > turns off Opening Cypress... > no welcome message 1`] = ` -"Found binary version 7.8.9 installed in: /cache/Cypress/1.2.3/Cypress.app - -⚠ Warning: Binary version 7.8.9 does not match the expected package version 1.2.3 - - These versions may not work properly together. - -" -`; - -exports[`lib/tasks/verify > smoke test with DEBUG output > finds ping value in the verbose output > verbose stdout output 1`] = ` -"It looks like this is your first time using Cypress: 1.2.3 - - -Opening Cypress... -" -`; - -exports[`lib/tasks/verify > when env var CYPRESS_RUN_BINARY > can log error to user on darwin > darwin: error when invalid CYPRESS_RUN_BINARY 1`] = ` -"Note: You have set the environment variable: - -CYPRESS_RUN_BINARY=/custom/ - -This overrides the default Cypress binary path used. - -Error: Could not run binary set by environment variable: CYPRESS_RUN_BINARY=/custom/ - -Ensure the environment variable is a path to the Cypress binary, matching **/Contents/MacOS/Cypress - ----------- - -ENOENT: no such file or directory, stat '/custom/' - ----------- - -Platform: darwin-x64 (undefined) -Cypress Version: 1.2.3 -" -`; - -exports[`lib/tasks/verify > when env var CYPRESS_RUN_BINARY > can log error to user on linux > linux: error when invalid CYPRESS_RUN_BINARY 1`] = ` -"Note: You have set the environment variable: - -CYPRESS_RUN_BINARY=/custom/ - -This overrides the default Cypress binary path used. - -Error: Could not run binary set by environment variable: CYPRESS_RUN_BINARY=/custom/ - -Ensure the environment variable is a path to the Cypress binary, matching **/Cypress - ----------- - -ENOENT: no such file or directory, stat '/custom/' - ----------- - -Platform: linux-x64 (undefined) -Cypress Version: 1.2.3 -" -`; - -exports[`lib/tasks/verify > when env var CYPRESS_RUN_BINARY > can log error to user on win32 > win32: error when invalid CYPRESS_RUN_BINARY 1`] = ` -"Note: You have set the environment variable: - -CYPRESS_RUN_BINARY=/custom/ - -This overrides the default Cypress binary path used. - -Error: Could not run binary set by environment variable: CYPRESS_RUN_BINARY=/custom/ - -Ensure the environment variable is a path to the Cypress binary, matching **/Cypress.exe - ----------- - -ENOENT: no such file or directory, stat '/custom/' - ----------- - -Platform: win32-x64 (undefined) -Cypress Version: 1.2.3 -" -`; - -exports[`lib/tasks/verify > when env var CYPRESS_RUN_BINARY > can validate and use executable > valid CYPRESS_RUN_BINARY 1`] = ` -"Note: You have set the environment variable: - -CYPRESS_RUN_BINARY=/custom/Contents/MacOS/Cypress - -This overrides the default Cypress binary path used. - -It looks like this is your first time using Cypress: 1.2.3 - - -Opening Cypress... -" -`; - -exports[`lib/tasks/verify > when running in CI > logs error when binary not found > error binary not found in ci 1`] = ` -"Error: The cypress npm package is installed, but the Cypress binary is missing. - -We expected the binary to be installed here: /cache/Cypress/1.2.3/Cypress.app/Contents/MacOS/Cypress - -Reasons it may be missing: - -- You're caching 'node_modules' but are not caching this path: /cache/Cypress -- You ran 'npm install' at an earlier build step but did not persist: /cache/Cypress - -Properly caching the binary will fix this error and avoid downloading and unzipping Cypress. - -Alternatively, you can run 'cypress install' to download the binary again. - -https://on.cypress.io/not-installed-ci-error - ----------- - -Platform: darwin-x64 (undefined) -Cypress Version: 1.2.3 -" -`; - -exports[`lib/tasks/verify > when running in CI > uses verbose renderer > verifying in ci 1`] = ` -"It looks like this is your first time using Cypress: 1.2.3 - - -Opening Cypress... -" -`; - -exports[`lib/tasks/verify > with force: true > clears verified version from state if verification fails > fails verifying Cypress 1`] = ` -" -Error: Cypress failed to start. - -This may be due to a missing library or dependency. https://on.cypress.io/required-dependencies - -Please refer to the error below for more details. - ----------- - -an error about dependencies - ----------- - -Platform: darwin-x64 (undefined) -Cypress Version: 1.2.3 -" -`; - -exports[`lib/tasks/verify > with force: true > shows full path to executable when verifying > verification with executable 1`] = ` -" - -Opening Cypress... -" -`; diff --git a/cli/test/lib/tasks/install.spec.ts b/cli/test/lib/tasks/install.spec.ts index 32db002a97..d026c48e89 100644 --- a/cli/test/lib/tasks/install.spec.ts +++ b/cli/test/lib/tasks/install.spec.ts @@ -2,6 +2,7 @@ import { vi, describe, it, beforeEach, afterEach, expect } from 'vitest' import os from 'os' import path from 'path' import chalk from 'chalk' +import stripAnsi from 'strip-ansi' import timers from 'timers/promises' import fs from 'fs-extra' import si, { Systeminformation } from 'systeminformation' @@ -121,18 +122,47 @@ vi.mock('../../../lib/tasks/state', async (importActual) => { }) const packageVersion = '1.2.3' +/** Override used with CYPRESS_INSTALL_BINARY in env-var warning tests */ +const envForcedBinaryVersion = '0.12.1' const downloadDestination = path.join(os.tmpdir(), `cypress-${process.pid}.zip`) const installDir = '/cache/Cypress/1.2.3' +/** Matches `logger.log(\`Installing Cypress ${chalk.gray(...)}\`)` prefix */ +const versionLabel = (version: string) => `version: ${version}` + +/** Substrings expected in stripAnsi(stdout) for install UX (stable across ANSI differences) */ +const INSTALL_OUTPUT = { + skipBinaryNote: 'Skipping binary installation', + cypressInstallBinaryZero: 'CYPRESS_INSTALL_BINARY = 0', + preReleaseBanner: 'pre-release build of Cypress', + cdnBetaBinaryPath: 'cdn.cypress.io/beta/binary', + installingCypress: 'Installing Cypress', + openingTheAppUrl: 'on.cypress.io/opening-the-app', + forceVersionBanner: 'Forcing a binary version different than the default', + finishedInstallation: 'Finished Installation', + skippingInstall: 'Skipping installation', + cliForceFlag: '--force', + stubBinaryVersionOther: 'x.x.x', + globalInstallWarning: 'installed Cypress globally', + devDependencyPerProject: 'devDependency per project', + npmUninstallGlobalCypress: 'npm uninstall -g cypress', + downloadingCypress: 'Downloading Cypress', + cacheWriteDenied: 'cannot write to the cache directory', + invalidCacheDir: '/invalid/cache/dir', + cachePermissionsIssueUrl: 'github.com/cypress-io/cypress/issues/1281', + eaccesCode: 'EACCES', + osUnsupported: 'could not be installed', + osRequirementsDoc: 'operating system requirements', + platformWin32Ia32: 'win32-ia32', +} as const + +const PRE_RELEASE_COMMIT_SHA = '3b7f0b5c59def1e9b5f385bd585c9b2836706c29' +const PRE_RELEASE_BRANCH = 'aBranchName' +const PRE_RELEASE_DOWNLOAD_URL = + `https://cdn.cypress.io/beta/binary/0.0.0-development/darwin-x64/${PRE_RELEASE_BRANCH}-${PRE_RELEASE_COMMIT_SHA}/cypress.zip` + /** - * NOTE: icons from listr2 do not render if process.stdout.isTTY is false, - * which does not exist when running in a worker thread, which is commonly the case in Vitest. - * - * This means that the test environment implicitly uses the VerboseRenderer as a fallback, - * where as the CLI uses the DefaultRenderer. - * - * This is the main reason the snapshots look different in testing mode vs when running the commands directly - * via the CLI. This also allows us for our snapshot tests to be deterministic because we aren't rerendering icon states. + * stdout capture: listr2/chalk emit ANSI; assertions use strip-ansi + toContain so tests stay stable across OS/CI. * * @see https://listr2.kilic.dev/renderer/renderer.html#frontmatter-title */ @@ -144,12 +174,15 @@ describe('/lib/tasks/install', function () { const createStdoutCapture = () => { const logs: string[] = [] - const originalOut = process.stdout.write + const originalOut = process.stdout.write.bind(process.stdout) - vi.spyOn(process.stdout, 'write').mockImplementation((strOrBugger: string | Uint8Array) => { - logs.push(strOrBugger as string) + vi.spyOn(process.stdout, 'write').mockImplementation(function ( + this: typeof process.stdout, + ...args: Parameters + ): boolean { + logs.push(args[0] as string) - return originalOut(strOrBugger) + return originalOut(...args) }) return () => logs.join('') @@ -176,6 +209,7 @@ describe('/lib/tasks/install', function () { afterEach(() => { globalThis.console = originalConsole // Restore original console chalk.level = previousChalkLevel + vi.unstubAllEnvs() }) describe('.start', function () { @@ -206,24 +240,29 @@ describe('/lib/tasks/install', function () { }) describe('skips install', function () { + beforeEach(() => { + vi.stubEnv('CYPRESS_INSTALL_BINARY', '0') + }) + it('when environment variable is set', async () => { const output = createStdoutCapture() - vi.stubEnv('CYPRESS_INSTALL_BINARY', '0') - await install.start() expect(download.start).not.toHaveBeenCalled() - expect(output()).toMatchSnapshot('skip installation 1') + const plain = stripAnsi(output()) + + expect(plain).toContain(INSTALL_OUTPUT.skipBinaryNote) + expect(plain).toContain(INSTALL_OUTPUT.cypressInstallBinaryZero) }) }) describe('non-stable builds', () => { const buildInfo = { stable: false, - commitSha: '3b7f0b5c59def1e9b5f385bd585c9b2836706c29', - commitBranch: 'aBranchName', + commitSha: PRE_RELEASE_COMMIT_SHA, + commitBranch: PRE_RELEASE_BRANCH, commitDate: new Date('1996-11-27').toISOString(), } @@ -235,7 +274,7 @@ describe('/lib/tasks/install', function () { await install.start({ buildInfo }) expect(download.start).toHaveBeenCalledWith(expect.objectContaining({ - version: 'https://cdn.cypress.io/beta/binary/0.0.0-development/darwin-x64/aBranchName-3b7f0b5c59def1e9b5f385bd585c9b2836706c29/cypress.zip', + version: PRE_RELEASE_DOWNLOAD_URL, })) }) @@ -243,7 +282,14 @@ describe('/lib/tasks/install', function () { const output = createStdoutCapture() await install.start({ buildInfo }) - expect(output()).toMatchSnapshot('pre-release warning') + const plain = stripAnsi(output()) + + expect(plain).toContain(INSTALL_OUTPUT.preReleaseBanner) + expect(plain).toContain(PRE_RELEASE_COMMIT_SHA) + expect(plain).toContain(PRE_RELEASE_BRANCH) + expect(plain).toContain(INSTALL_OUTPUT.cdnBetaBinaryPath) + expect(plain).toContain(INSTALL_OUTPUT.installingCypress) + expect(plain).toContain(INSTALL_OUTPUT.openingTheAppUrl) }) it('installs to the expected pre-release cache dir', async function () { @@ -253,7 +299,7 @@ describe('/lib/tasks/install', function () { await install.start({ buildInfo }) expect(unzip.start).toHaveBeenCalledWith(expect.objectContaining({ - installDir: expect.stringMatching(/\/Cypress\/beta\-1\.2\.3\-aBranchName\-3b7f0b5c$/), + installDir: expect.stringMatching(/beta\-1\.2\.3\-aBranchName\-3b7f0b5c$/), })) }) }) @@ -262,7 +308,7 @@ describe('/lib/tasks/install', function () { it('warns when specifying cypress version in env', async function () { const output = createStdoutCapture() - const version = '0.12.1' + const version = envForcedBinaryVersion vi.stubEnv('CYPRESS_INSTALL_BINARY', version) @@ -275,7 +321,13 @@ describe('/lib/tasks/install', function () { zipFilePath: downloadDestination, })) - expect(output()).toMatchSnapshot('specify version in env vars 1') + const plain = stripAnsi(output()) + + expect(plain).toContain(INSTALL_OUTPUT.forceVersionBanner) + expect(plain).toContain(packageVersion) + expect(plain).toContain(envForcedBinaryVersion) + expect(plain).toContain(INSTALL_OUTPUT.installingCypress) + expect(plain).toContain(INSTALL_OUTPUT.openingTheAppUrl) }) it('trims environment variable before installing', async function () { @@ -387,7 +439,10 @@ describe('/lib/tasks/install', function () { await install.start() - expect(output()).toMatchSnapshot('version already installed - cypress install 1') + const plain = stripAnsi(output()) + + expect(plain).toContain(INSTALL_OUTPUT.skippingInstall) + expect(plain).toContain(INSTALL_OUTPUT.cliForceFlag) }) it('logs when already installed when run from postInstall', async function () { @@ -397,7 +452,10 @@ describe('/lib/tasks/install', function () { await install.start() - expect(output()).toMatchSnapshot('version already installed - postInstall 1') + const plain = stripAnsi(output()) + + expect(plain).toContain(packageVersion) + expect(plain).toContain(installDir) }) }) @@ -417,7 +475,13 @@ describe('/lib/tasks/install', function () { installDir, })) - expect(output()).toMatchSnapshot('continues installing on failure 1') + const plain = stripAnsi(output()) + + expect(plain).toContain(INSTALL_OUTPUT.installingCypress) + expect(plain).toContain(versionLabel(packageVersion)) + expect(plain).toContain(INSTALL_OUTPUT.finishedInstallation) + expect(plain).toContain(installDir) + expect(plain).toContain(INSTALL_OUTPUT.openingTheAppUrl) }) }) @@ -442,7 +506,13 @@ describe('/lib/tasks/install', function () { downloadDestination, ) - expect(output()).toMatchSnapshot('installs without existing installation 1') + const plain = stripAnsi(output()) + + expect(plain).toContain(INSTALL_OUTPUT.installingCypress) + expect(plain).toContain(versionLabel(packageVersion)) + expect(plain).toContain(INSTALL_OUTPUT.finishedInstallation) + expect(plain).toContain(installDir) + expect(plain).toContain(INSTALL_OUTPUT.openingTheAppUrl) }) }) @@ -450,7 +520,7 @@ describe('/lib/tasks/install', function () { it('logs message and starts download', async function () { const output = createStdoutCapture() - vi.mocked(state.getBinaryPkgAsync).mockResolvedValue({ version: 'x.x.x' }) + vi.mocked(state.getBinaryPkgAsync).mockResolvedValue({ version: INSTALL_OUTPUT.stubBinaryVersionOther }) await install.start() expect(download.start).toHaveBeenCalledWith(expect.objectContaining({ @@ -461,7 +531,13 @@ describe('/lib/tasks/install', function () { installDir, })) - expect(output()).toMatchSnapshot('installed version does not match needed version 1') + const plain = stripAnsi(output()) + + expect(plain).toContain(INSTALL_OUTPUT.stubBinaryVersionOther) + expect(plain).toContain(installDir) + expect(plain).toContain(INSTALL_OUTPUT.installingCypress) + expect(plain).toContain(INSTALL_OUTPUT.finishedInstallation) + expect(plain).toContain(INSTALL_OUTPUT.openingTheAppUrl) }) }) @@ -480,7 +556,13 @@ describe('/lib/tasks/install', function () { installDir, })) - expect(output()).toMatchSnapshot('forcing true always installs 1') + const plain = stripAnsi(output()) + + expect(plain).toContain(packageVersion) + expect(plain).toContain(installDir) + expect(plain).toContain(INSTALL_OUTPUT.installingCypress) + expect(plain).toContain(INSTALL_OUTPUT.finishedInstallation) + expect(plain).toContain(INSTALL_OUTPUT.openingTheAppUrl) }) }) @@ -490,7 +572,7 @@ describe('/lib/tasks/install', function () { vi.mocked(util.isInstalledGlobally).mockReturnValue(true) - vi.mocked(state.getBinaryPkgAsync).mockResolvedValue({ version: 'x.x.x' }) + vi.mocked(state.getBinaryPkgAsync).mockResolvedValue({ version: INSTALL_OUTPUT.stubBinaryVersionOther }) await install.start() @@ -502,7 +584,12 @@ describe('/lib/tasks/install', function () { installDir, })) - expect(output()).toMatchSnapshot('warning installing as global 1') + const plain = stripAnsi(output()) + + expect(plain).toContain(INSTALL_OUTPUT.globalInstallWarning) + expect(plain).toContain(INSTALL_OUTPUT.devDependencyPerProject) + expect(plain).toContain(INSTALL_OUTPUT.npmUninstallGlobalCypress) + expect(plain).toContain(INSTALL_OUTPUT.finishedInstallation) }) }) @@ -512,11 +599,17 @@ describe('/lib/tasks/install', function () { vi.mocked(util.isCi).mockReturnValue(true) - vi.mocked(state.getBinaryPkgAsync).mockResolvedValue({ version: 'x.x.x' }) + vi.mocked(state.getBinaryPkgAsync).mockResolvedValue({ version: INSTALL_OUTPUT.stubBinaryVersionOther }) await install.start() - expect(output()).toMatchSnapshot('installing in ci 1') + const plain = stripAnsi(output()) + + expect(plain).toContain(INSTALL_OUTPUT.installingCypress) + expect(plain).toContain(INSTALL_OUTPUT.downloadingCypress) + expect(plain).toContain(INSTALL_OUTPUT.finishedInstallation) + expect(plain).toContain(installDir) + expect(plain).toContain(INSTALL_OUTPUT.openingTheAppUrl) }) }) @@ -542,7 +635,12 @@ describe('/lib/tasks/install', function () { expect(message).not.toEqual('should have caught error') logger.error(err) - expect(output()).toMatchSnapshot('invalid cache directory 1') + const plain = stripAnsi(output()) + + expect(plain).toContain(INSTALL_OUTPUT.cacheWriteDenied) + expect(plain).toContain(INSTALL_OUTPUT.invalidCacheDir) + expect(plain).toContain(INSTALL_OUTPUT.cachePermissionsIssueUrl) + expect(plain).toContain(INSTALL_OUTPUT.eaccesCode) } }) }) @@ -617,7 +715,7 @@ describe('/lib/tasks/install', function () { await install.start() - expect(output()).toMatchSnapshot('silent install 1') + expect(output()).toBe('') }) it('exits with error when installing on unsupported os', async function () { @@ -634,7 +732,11 @@ describe('/lib/tasks/install', function () { expect(message).not.toEqual('should have caught error') logger.error(err) - expect(output()).toMatchSnapshot('error when installing on unsupported os') + const plain = stripAnsi(output()) + + expect(plain).toContain(INSTALL_OUTPUT.osUnsupported) + expect(plain).toContain(INSTALL_OUTPUT.osRequirementsDoc) + expect(plain).toContain(INSTALL_OUTPUT.platformWin32Ia32) } }) }) diff --git a/cli/test/lib/tasks/verify.spec.ts b/cli/test/lib/tasks/verify.spec.ts index 5bef54653a..8f0e5e29b7 100644 --- a/cli/test/lib/tasks/verify.spec.ts +++ b/cli/test/lib/tasks/verify.spec.ts @@ -10,6 +10,8 @@ import { Console } from 'console' import fs from 'fs-extra' import si, { Systeminformation } from 'systeminformation' import _xvfb from '@cypress/xvfb' +import stripAnsi from 'strip-ansi' +import { Listr } from 'listr2' import util from '../../../lib/util' import logger from '../../../lib/logger' @@ -116,19 +118,39 @@ vi.mock('../../../lib/util', async (importActual) => { } }) +vi.mock('listr2', async (importActual) => { + const actual = await importActual() + const { Listr } = actual + + return { + ...actual, + Listr: vi.fn(function (this: unknown, ...args: ConstructorParameters) { + return new Listr(...args) + }), + } +}) + describe('lib/tasks/verify', () => { const createStdoutCapture = () => { const logs: string[] = [] const originalOut = process.stdout.write - vi.spyOn(process.stdout, 'write').mockImplementation((strOrBugger: string | Uint8Array) => { - logs.push(strOrBugger as string) + vi.spyOn(process.stdout, 'write').mockImplementation((...args: Parameters) => { + const chunk = args[0] - return originalOut(strOrBugger) + if (typeof chunk === 'string') { + logs.push(chunk) + } + + // Must preserve `stdout` as `this` — unbound calls break Writable (_writableState). + return originalOut.apply(process.stdout, args) }) return () => logs.join('') } + + /** listr2/chalk add ANSI; strip for stable substring assertions. */ + const plain = (getOutput: () => string): string => stripAnsi(getOutput()) let spawnedProcess: any // Direct console to process.stdout/stderr let originalConsole: Console @@ -143,6 +165,8 @@ describe('lib/tasks/verify', () => { vi.unstubAllEnvs() vi.stubEnv('npm_config_loglevel', 'notice') + // Align `state.getCacheDir()` with mock-fs fixtures (stable across CI/sandbox vs real cachedir()). + vi.stubEnv('CYPRESS_CACHE_FOLDER', '/cache/Cypress') originalConsole = globalThis.console @@ -216,7 +240,7 @@ describe('lib/tasks/verify', () => { it('returns early when `CYPRESS_SKIP_VERIFY` is set to true', async () => { vi.stubEnv('CYPRESS_SKIP_VERIFY', 'true') - const result = await start({ listrRenderer: 'silent' }) + const result = await start() expect(result).toEqual(undefined) }) @@ -225,7 +249,7 @@ describe('lib/tasks/verify', () => { const output = createStdoutCapture() try { - await start({ listrRenderer: 'silent' }) + await start() throw new Error('should have caught error') } catch (err) { const message = err instanceof Error ? err.message : String(err) @@ -233,7 +257,11 @@ describe('lib/tasks/verify', () => { expect(message).not.toContain('should have caught error') logger.error(err) - expect(output()).toMatchSnapshot() + const out = plain(output) + + expect(out).toContain('No version of Cypress is installed') + expect(out).toContain('/cache/Cypress/1.2.3/Cypress.app') + expect(out).toContain('cypress install') } }) @@ -248,7 +276,7 @@ describe('lib/tasks/verify', () => { // @ts-expect-error - geteuid is potentially undefined vi.mocked(geteuid).mockReturnValue(0) // user is root - await start({ listrRenderer: 'silent' }) + await start() expect(util.exec).toHaveBeenCalledWith(executablePath, ['--no-sandbox', '--smoke-test', '--ping=222'], expect.anything()) }) @@ -264,7 +292,7 @@ describe('lib/tasks/verify', () => { // @ts-expect-error - geteuid is potentially undefined vi.mocked(geteuid).mockReturnValue(1000) // user is non-root - await start({ listrRenderer: 'silent' }) + await start() expect(util.exec).toHaveBeenCalledWith(executablePath, ['--no-sandbox', '--smoke-test', '--ping=222'], expect.anything()) }) @@ -279,7 +307,7 @@ describe('lib/tasks/verify', () => { packageVersion, }) - await start({ listrRenderer: 'silent' }) + await start() expect(output()).toEqual('') @@ -295,16 +323,20 @@ describe('lib/tasks/verify', () => { packageVersion: 'bloop', }) - await start({ listrRenderer: 'silent' }) + await start() - expect(output()).toMatchSnapshot() + const out = plain(output) + + expect(out).toContain('Found binary version bloop') + expect(out).toContain('does not match the expected package version') + expect(out).toContain('1.2.3') }) it('logs error and exits when executable cannot be found', async () => { const output = createStdoutCapture() try { - await start({ listrRenderer: 'silent' }) + await start() throw new Error('should have caught error') } catch (err) { const message = err instanceof Error ? err.message : String(err) @@ -312,7 +344,10 @@ describe('lib/tasks/verify', () => { expect(message).not.toContain('should have caught error') logger.error(err) - expect(output()).toMatchSnapshot() + const out = plain(output) + + expect(out).toContain('No version of Cypress is installed') + expect(out).toContain('cypress install') } }) @@ -332,10 +367,14 @@ describe('lib/tasks/verify', () => { }) try { - await start({ smokeTestTimeout: 1, listrRenderer: 'silent' }) + await start({ smokeTestTimeout: 1 }) } catch (err) { logger.error(err) - expect(output()).toMatchSnapshot() + + const out = plain(output) + + expect(out).toContain('Cypress verification timed out') + expect(out).toContain('some stderr') } }) @@ -355,10 +394,15 @@ describe('lib/tasks/verify', () => { }) try { - await start({ smokeTestTimeout: 1, listrRenderer: 'silent' }) + await start({ smokeTestTimeout: 1 }) } catch (err) { logger.error(err) - expect(output()).toMatchSnapshot() + + const out = plain(output) + + expect(out).toContain('Cypress failed to start') + expect(out).toContain('some stderr') + expect(out).toContain('on.cypress.io/required-dependencies') } }) @@ -377,10 +421,14 @@ describe('lib/tasks/verify', () => { }) try { - await start({ smokeTestTimeout: 1, listrRenderer: 'silent' }) + await start({ smokeTestTimeout: 1 }) } catch (err) { logger.error(err) - expect(output()).toMatchSnapshot() + + const out = plain(output) + + expect(out).toContain('Cypress failed to start') + expect(out).toContain('on.cypress.io/required-dependencies') } }) @@ -402,7 +450,7 @@ describe('lib/tasks/verify', () => { stderr: '', } as any) - await start({ listrRenderer: 'silent' }) + await start() expect(util.exec).toHaveBeenCalledWith( executablePath, @@ -424,9 +472,9 @@ describe('lib/tasks/verify', () => { it('shows full path to executable when verifying', async () => { const output = createStdoutCapture() - await start({ force: true, listrRenderer: 'silent' }) + await start({ force: true }) - expect(output()).toMatchSnapshot('verification with executable') + expect(plain(output)).toContain('Opening Cypress...') }) it('clears verified version from state if verification fails', async () => { @@ -438,7 +486,7 @@ describe('lib/tasks/verify', () => { }) try { - await start({ force: true, listrRenderer: 'silent' }) + await start({ force: true }) throw new Error('Should have thrown') } catch (err) { logger.error(err) @@ -448,7 +496,10 @@ describe('lib/tasks/verify', () => { expect(exists).toEqual(false) - expect(output()).toMatchSnapshot('fails verifying Cypress') + const out = plain(output) + + expect(out).toContain('Cypress failed to start') + expect(out).toContain('an error about dependencies') }) }) @@ -482,9 +533,10 @@ describe('lib/tasks/verify', () => { it('finds ping value in the verbose output', async () => { const output = createStdoutCapture() - await start({ listrRenderer: 'silent' }) + await start() - expect(output()).toMatchSnapshot('verbose stdout output') + expect(util.exec).toHaveBeenCalled() + expect(plain(output)).toContain('Opening Cypress...') }) }) @@ -533,7 +585,7 @@ describe('lib/tasks/verify', () => { return Promise.reject(firstSpawnError) }) - await start({ listrRenderer: 'silent' }) + await start() expect(util.exec).toHaveBeenCalledTimes(2) // user should have been warned @@ -581,19 +633,21 @@ describe('lib/tasks/verify', () => { }) try { - await start({ listrRenderer: 'silent' }) + await start() } catch (e) { const message = e instanceof Error ? e.message : String(e) expect(util.exec).toHaveBeenCalledTimes(2) // second time around we should have called Xvfb - expect(xvfb.start).toHaveBeenCalledOnce - expect(xvfb.stop).toHaveBeenCalledOnce + expect(xvfb.start).toHaveBeenCalledTimes(1) + expect(xvfb.stop).toHaveBeenCalledTimes(1) // user should have been warned expect(loggerWarnSpy).toHaveBeenCalledWith(expect.stringContaining('DISPLAY was set to: "test-display"')) - expect(message).toMatchSnapshot('tried to verify twice, on the first try got the DISPLAY error') + expect(message).toContain('Cypress verification failed') + expect(message).toContain('Gtk: cannot open display: 987') + expect(message).toContain('some other error') return } @@ -611,11 +665,14 @@ describe('lib/tasks/verify', () => { }) try { - await start({ listrRenderer: 'silent' }) + await start() } catch (err) { logger.error(err) - expect(output()).toMatchSnapshot('no Cypress executable') + const out = plain(output) + + expect(out).toContain('No version of Cypress is installed') + expect(out).toContain('cypress install') return } @@ -635,11 +692,14 @@ describe('lib/tasks/verify', () => { }) try { - await start({ listrRenderer: 'silent' }) + await start() } catch (err) { logger.error(err) - expect(output()).toMatchSnapshot('Cypress non-executable permission') + const out = plain(output) + + expect(out).toContain('does not have executable permissions') + expect(out).toContain(executablePath) return } @@ -656,9 +716,12 @@ describe('lib/tasks/verify', () => { packageVersion, }) - await start({ listrRenderer: 'silent' }) + await start() - expect(output()).toMatchSnapshot('current version has not been verified') + const out = plain(output) + + expect(out).toContain('first time using Cypress') + expect(out).toContain('Opening Cypress...') }) it('logs and runs when installed version is different than package version', async () => { @@ -670,9 +733,13 @@ describe('lib/tasks/verify', () => { packageVersion: '7.8.9', }) - await start({ listrRenderer: 'silent' }) + await start() - expect(output()).toMatchSnapshot('different version installed') + const out = plain(output) + + expect(out).toContain('Found binary version 7.8.9') + expect(out).toContain('does not match the expected package version') + expect(out).toContain('first time using Cypress') }) it('is silent when logLevel is silent', async () => { @@ -686,9 +753,9 @@ describe('lib/tasks/verify', () => { vi.stubEnv('npm_config_loglevel', 'silent') - await start({ listrRenderer: 'silent' }) + await start() - expect(output()).toMatchSnapshot('silent verify') + expect(output()).toEqual('') }) it('turns off Opening Cypress...', async () => { @@ -702,7 +769,11 @@ describe('lib/tasks/verify', () => { await start({ welcomeMessage: false }) - expect(output()).toMatchSnapshot('no welcome message') + const out = plain(output) + + expect(out).not.toContain('Opening Cypress...') + expect(out).toContain('7.8.9') + expect(out).toContain('does not match') }) it('logs error when fails smoke test unexpectedly without stderr', async () => { @@ -721,11 +792,14 @@ describe('lib/tasks/verify', () => { }) try { - await start({ listrRenderer: 'silent' }) + await start() } catch (err) { logger.error(err) - expect(output()).toMatchSnapshot('fails with no stderr') + const out = plain(output) + + expect(out).toContain('Cypress failed to start') + expect(out).toContain('EPERM NOT PERMITTED') return } @@ -746,13 +820,13 @@ describe('lib/tasks/verify', () => { }) it('starts xvfb', async () => { - await start({ listrRenderer: 'silent' }) + await start() expect(xvfb.start).toHaveBeenCalled() }) it('stops xvfb on spawned process close', async () => { - await start({ listrRenderer: 'silent' }) + await start() expect(xvfb.stop).toHaveBeenCalled() }) @@ -776,13 +850,14 @@ describe('lib/tasks/verify', () => { }) try { - await start({ listrRenderer: 'silent' }) + await start() } catch (err) { - expect(xvfb.stop).toHaveBeenCalledOnce - logger.error(err) - expect(output()).toMatchSnapshot('xvfb fails') + const out = plain(output) + + expect(out).toContain('Xvfb exited') + expect(out).toContain('test without xvfb') return } @@ -802,25 +877,21 @@ describe('lib/tasks/verify', () => { vi.mocked(util.isCi).mockReturnValue(true) }) - it('uses verbose renderer', async () => { - const output = createStdoutCapture() - - await start({ listrRenderer: 'silent' }) - - expect(output()).toMatchSnapshot('verifying in ci') - }) - it('logs error when binary not found', async () => { const output = createStdoutCapture() mockfs({}) try { - await start({ listrRenderer: 'silent' }) + await start() } catch (err) { logger.error(err) - expect(output()).toMatchSnapshot('error binary not found in ci') + const out = plain(output) + + expect(out).toContain('Cypress binary is missing') + expect(out).toContain('/cache/Cypress') + expect(out).toContain('not-installed-ci-error') return } @@ -854,10 +925,10 @@ describe('lib/tasks/verify', () => { return Promise.reject(new Error('should have caught error')) }) - await start({ listrRenderer: 'silent' }) + await start() expect(util.exec).toHaveBeenCalledWith(realEnvBinaryPath, ['--no-sandbox', '--smoke-test', '--ping=222'], expect.anything()) - expect(output()).toMatchSnapshot('valid CYPRESS_RUN_BINARY') + expect(plain(output)).toContain('Opening Cypress...') }) for (const platform of ['darwin', 'linux', 'win32']) { @@ -869,10 +940,22 @@ describe('lib/tasks/verify', () => { vi.mocked(os.platform).mockReturnValue(platform as NodeJS.Platform) try { - await start({ listrRenderer: 'silent' }) + await start() } catch (err) { logger.error(err) - expect(output()).toMatchSnapshot(`${platform}: error when invalid CYPRESS_RUN_BINARY`) + + const out = plain(output) + + expect(out).toContain('Could not run binary set by environment variable') + expect(out).toContain('ENOENT') + + if (platform === 'darwin') { + expect(out).toContain('Contents/MacOS/Cypress') + } else if (platform === 'linux') { + expect(out).toContain('**/Cypress') + } else { + expect(out).toContain('Cypress.exe') + } return } diff --git a/cli/vitest.config.ts b/cli/vitest.config.ts index 1a9a321880..e78c9c7304 100644 --- a/cli/vitest.config.ts +++ b/cli/vitest.config.ts @@ -5,5 +5,6 @@ export default defineConfig({ include: ['test/**/*.spec.ts'], globals: true, environment: 'node', + reporters: ['default'], }, }) diff --git a/package.json b/package.json index fc3ef2bfcb..c23ef785b9 100644 --- a/package.json +++ b/package.json @@ -165,7 +165,7 @@ "lazy-ass": "1.6.0", "lerna": "8.1.9", "lint-staged": "^16", - "listr2": "3.8.3", + "listr2": "^9.0.5", "lodash": "^4.17.21", "minimist": "1.2.8", "mobx": "6.13.6", diff --git a/scripts/type_check.js b/scripts/type_check.js index 2cb84b834b..b5745d0cbe 100644 --- a/scripts/type_check.js +++ b/scripts/type_check.js @@ -59,11 +59,12 @@ program concurrent: process.env.CI ? 4 : 1, renderer: process.env.CI ? 'verbose' : 'default', exitOnError: false, + collectErrors: 'minimal', }) tasks.run() .then(() => { - if (tasks.err[0] && tasks.err[0].errors.length > 0) { + if (tasks.errors.length > 0) { process.exitCode = 1 log('') diff --git a/yarn.lock b/yarn.lock index 9d6d7129d5..407d67e8a9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10752,7 +10752,7 @@ ansi-escapes@^3.0.0, ansi-escapes@^3.2.0: resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== -ansi-escapes@^4.2.1, ansi-escapes@^4.3.0, ansi-escapes@^4.3.1, ansi-escapes@^4.3.2: +ansi-escapes@^4.2.1, ansi-escapes@^4.3.1, ansi-escapes@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== @@ -13341,7 +13341,7 @@ color-support@1.1.3, color-support@^1.1.3: resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== -colorette@^1.1.0, colorette@^1.2.2: +colorette@^1.1.0: version "1.2.2" resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== @@ -15492,14 +15492,6 @@ enhanced-resolve@^5.0.0, enhanced-resolve@^5.17.1, enhanced-resolve@^5.7.0, enha graceful-fs "^4.2.4" tapable "^2.2.0" -enquirer@^2.3.6: - version "2.4.1" - resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.4.1.tgz#93334b3fbd74fc7097b224ab4a8fb7e40bf4ae56" - integrity sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ== - dependencies: - ansi-colors "^4.1.1" - strip-ansi "^6.0.1" - enquirer@~2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" @@ -17027,7 +17019,7 @@ fetch-blob@^3.1.2, fetch-blob@^3.1.4: node-domexception "^1.0.0" web-streams-polyfill "^3.0.3" -figures@3.2.0, figures@^3.0.0, figures@^3.2.0: +figures@3.2.0, figures@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== @@ -22196,22 +22188,7 @@ listr-verbose-renderer@^0.5.0: date-fns "^1.27.2" figures "^2.0.0" -listr2@3.8.3, listr2@^3.8.3: - version "3.8.3" - resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.8.3.tgz#531f86870440172accd078ca4ebf4870ff5d850e" - integrity sha512-2NUXrFxPeccawyOpJXfUuV7FmKG4ummQjY+ByPkhTFJ5dVxU9EQbLh0PzaNSXlgbIK7Kmam18oz+ev8sbukzBA== - dependencies: - cli-truncate "^2.1.0" - colorette "^1.2.2" - figures "^3.2.0" - indent-string "^4.0.0" - log-update "^4.0.0" - p-map "^4.0.0" - rxjs "^7.1.0" - through "^2.3.8" - wrap-ansi "^7.0.0" - -listr2@^9.0.3: +listr2@^9.0.3, listr2@^9.0.5: version "9.0.5" resolved "https://registry.yarnpkg.com/listr2/-/listr2-9.0.5.tgz#92df7c4416a6da630eb9ef46da469b70de97b316" integrity sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g== @@ -22643,16 +22620,6 @@ log-update@^2.3.0: cli-cursor "^2.0.0" wrap-ansi "^3.0.1" -log-update@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" - integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== - dependencies: - ansi-escapes "^4.3.0" - cli-cursor "^3.1.0" - slice-ansi "^4.0.0" - wrap-ansi "^6.2.0" - log-update@^6.1.0: version "6.1.0" resolved "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz#1a04ff38166f94647ae1af562f4bd6a15b1b7cd4" @@ -28169,7 +28136,7 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -rxjs@7.8.2, rxjs@^7.1.0, rxjs@^7.5.5, rxjs@~7.8.2: +rxjs@7.8.2, rxjs@^7.5.5, rxjs@~7.8.2: version "7.8.2" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b" integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== @@ -29007,15 +28974,6 @@ slice-ansi@^3.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" -slice-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" - integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== - dependencies: - ansi-styles "^4.0.0" - astral-regex "^2.0.0" - is-fullwidth-code-point "^3.0.0" - slice-ansi@^7.1.0: version "7.1.0" resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz#cd6b4655e298a8d1bdeb04250a433094b347b9a9"