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 <bglesias@gmail.com>

* 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 <bglesias@gmail.com>
Co-authored-by: Matt Schile <mschile@cypress.io>
This commit is contained in:
Cacie Prins
2026-04-28 15:06:42 -04:00
committed by GitHub
parent ea98906633
commit 545556ee30
14 changed files with 468 additions and 1105 deletions
+1
View File
@@ -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)
-75
View File
@@ -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
+122 -133
View File
@@ -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<RegExpMatchArray | null> => {
const platformInfo = await util.getPlatformInfo()
@@ -151,7 +99,7 @@ const validateOS = async (): Promise<RegExpMatchArray | null> => {
* 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<any> => {
interface StartOptions {
force?: boolean
buildInfo?: CypressBuildInfo
}
const start = async (options: StartOptions = {}): Promise<ListrContext | void> => {
debug('installing with options %j', options)
const envVarVersion = getEnvVarVersion()
@@ -251,16 +204,16 @@ const start = async (options: any = {}): Promise<any> => {
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<any> => {
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<any> => {
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,
+50 -73
View File
@@ -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<any> {
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<void> {
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<void> => {
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<void> => {
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<void> => {
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
-8
View File
@@ -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
},
+1 -4
View File
@@ -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",
@@ -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.
"
`;
@@ -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...
"
`;
+138 -36
View File
@@ -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<typeof process.stdout.write>
): 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)
}
})
})
+147 -64
View File
@@ -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<typeof import('listr2')>()
const { Listr } = actual
return {
...actual,
Listr: vi.fn(function (this: unknown, ...args: ConstructorParameters<typeof Listr>) {
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<typeof process.stdout.write>) => {
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
}
+1
View File
@@ -5,5 +5,6 @@ export default defineConfig({
include: ['test/**/*.spec.ts'],
globals: true,
environment: 'node',
reporters: ['default'],
},
})
+1 -1
View File
@@ -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",
+2 -1
View File
@@ -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('')
+5 -47
View File
@@ -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"