diff --git a/.github/workflows/pipeline-segment-electron-test.yml b/.github/workflows/pipeline-segment-electron-test.yml
index 7f4417f3bd4..b47cdf33286 100644
--- a/.github/workflows/pipeline-segment-electron-test.yml
+++ b/.github/workflows/pipeline-segment-electron-test.yml
@@ -110,11 +110,6 @@ jobs:
configure_sys_tccdb "$values"
fi
done
-
- # Ref: https://github.com/getsentry/sentry-cocoa/blob/main/scripts/ci-enable-permissions.sh
- if [ "$OSTYPE" = "darwin24" ]; then
- defaults write ~/Library/Group\ Containers/group.com.apple.replayd/ScreenCaptureApprovals.plist "/bin/bash" -date "3024-09-23 12:00:00 +0000"
- fi
- name: Turn off the unexpectedly quit dialog on macOS
if: ${{ inputs.target-platform == 'macos' }}
run: defaults write com.apple.CrashReporter DialogType server
@@ -127,6 +122,12 @@ jobs:
path: src/electron
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
+ - name: Turn off screenshot nag on macOS
+ if: ${{ inputs.target-platform == 'macos' }}
+ run: |
+ defaults write ~/Library/Group\ Containers/group.com.apple.replayd/ScreenCaptureApprovals.plist "/bin/bash" -date "3024-09-23 12:00:00 +0000"
+ src/electron/script/actions/screencapture-nag-remover.sh -a $(which bash)
+ src/electron/script/actions/screencapture-nag-remover.sh -a /opt/hca/hosted-compute-agent
- name: Setup SSH Debugging
if: ${{ inputs.target-platform == 'macos' && (inputs.enable-ssh || env.ACTIONS_STEP_DEBUG == 'true') }}
uses: ./src/electron/.github/actions/ssh-debug
@@ -227,7 +228,7 @@ jobs:
export ELECTRON_FORCE_TEST_SUITE_EXIT="true"
fi
fi
- node script/yarn test --runners=main --trace-uncaught --enable-logging --files $tests_files
+ node script/yarn test --runners=main --enableRerun=3 --trace-uncaught --enable-logging --files $tests_files
else
chown :builduser .. && chmod g+w ..
chown -R :builduser . && chmod -R g+w .
diff --git a/package.json b/package.json
index d45a2e60e41..4ef5e62a0d3 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"@types/temp": "^0.9.4",
"@typescript-eslint/eslint-plugin": "^8.32.1",
"@typescript-eslint/parser": "^8.7.0",
+ "@xmldom/xmldom": "^0.8.11",
"buffer": "^6.0.3",
"chalk": "^4.1.0",
"check-for-leaks": "^1.2.1",
diff --git a/script/actions/screencapture-nag-remover.sh b/script/actions/screencapture-nag-remover.sh
new file mode 100755
index 00000000000..b62024fb2a3
--- /dev/null
+++ b/script/actions/screencapture-nag-remover.sh
@@ -0,0 +1,297 @@
+#!/bin/bash
+# From https://github.com/luckman212/screencapture-nag-remover
+
+SELF='screencapture-nag-remover'
+FQPN=$(realpath "$0")
+PLIST="$HOME/Library/Group Containers/group.com.apple.replayd/ScreenCaptureApprovals.plist"
+AGENT_PLIST="$HOME/Library/LaunchAgents/$SELF.plist"
+MDM_PROFILE="$HOME/Downloads/macOS_15.1_DisableScreenCaptureAlerts.mobileconfig"
+TCC_DB='/Library/Application Support/com.apple.TCC/TCC.db'
+FUTURE=$(/bin/date -j -v+100y +"%Y-%m-%d %H:%M:%S +0000")
+INTERVAL=86400 #run every 24h
+
+IFS='.' read -r MAJ MIN _ < <(/usr/bin/sw_vers --productVersion)
+if (( MAJ < 15 )); then
+ echo >&2 "this tool requires macOS 15 (Sequoia)"
+ exit
+fi
+
+_os_is_151_or_higher() {
+ (( MAJ >= 15 )) && (( MIN > 0 ))
+}
+
+_fda_settings() {
+ /usr/bin/open 'x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles'
+}
+
+_open_device_management() {
+ /usr/bin/open 'x-apple.systempreferences:com.apple.preferences.configurationprofiles'
+}
+
+_bundleid_to_name() {
+ local APP_NAME
+ APP_NAME=$(/usr/bin/mdfind kMDItemCFBundleIdentifier == "$1" 2>/dev/null)
+ echo "${APP_NAME##*/}"
+}
+
+_create_plist() {
+ cat <<-EOF 2>/dev/null >"$PLIST"
+
+
+
+
+
+
+ EOF
+}
+
+_bounce_daemons() {
+ /usr/bin/killall -HUP replayd
+ /usr/bin/killall -u "$USER" cfprefsd
+}
+
+_nagblock() {
+ local APP_NAME
+ if _os_is_151_or_higher; then
+ if [[ -z $1 ]]; then
+ echo >&2 "supply the bundle ID of the app"
+ return 1
+ fi
+ APP_NAME=$(_bundleid_to_name "$1")
+ echo "disabling nag for $1${APP_NAME:+ ($APP_NAME)}"
+ /usr/bin/defaults write "$PLIST" "$1" -dict \
+ kScreenCaptureApprovalLastAlerted -date "$FUTURE" \
+ kScreenCaptureApprovalLastUsed -date "$FUTURE"
+ (( c++ ))
+ else
+ if [[ -z $1 ]]; then
+ echo >&2 "supply complete pathname to the binary inside the app bundle"
+ return 1
+ fi
+ [[ -e $1 ]] || { echo >&2 "$1 does not exist"; return 1; }
+ IFS='/' read -ra PARTS <<< "$1"
+ for p in "${PARTS[@]}"; do
+ if [[ $p == *.app ]]; then
+ APP_NAME=$p
+ break
+ fi
+ done
+ echo "disabling nag for ${APP_NAME:-$1}"
+ /usr/bin/defaults write "$PLIST" "$1" -date "$FUTURE"
+ (( c++ ))
+ return 0
+ fi
+}
+
+_enum_apps() {
+ [[ -e $PLIST ]] || return 1
+ if _os_is_151_or_higher; then
+ /usr/bin/plutil -convert raw -o - -- "$PLIST"
+ else
+ /usr/bin/plutil -convert xml1 -o - -- "$PLIST" |
+ /usr/bin/sed -n "s/.*\(.*\)<\/key>.*/\1/p"
+ fi
+}
+
+_generate_mdm_profile() {
+UUID1=$(/usr/bin/uuidgen)
+UUID2=$(/usr/bin/uuidgen)
+/bin/cat <"$MDM_PROFILE"
+
+
+
+
+ PayloadContent
+
+
+ PayloadDisplayName
+ Restrictions
+ PayloadIdentifier
+ com.apple.applicationaccess.${UUID2}
+ PayloadType
+ com.apple.applicationaccess
+ PayloadUUID
+ ${UUID2}
+ PayloadVersion
+ 1
+ forceBypassScreenCaptureAlert
+
+
+
+ PayloadDescription
+ Disables additional screen capture alerts on macOS 15.1 or higher
+ PayloadDisplayName
+ DisableScreenCaptureAlert
+ PayloadIdentifier
+ com.apple.applicationaccess.forceBypassScreenCaptureAlert
+ PayloadScope
+ System
+ PayloadType
+ Configuration
+ PayloadUUID
+ ${UUID1}
+ PayloadVersion
+ 1
+ TargetDeviceType
+ 5
+
+
+EOF
+#Apple prohibits self-installing TCC profiles, they can only be pushed via MDM
+#/usr/bin/open "$MDM_PROFILE"
+#_open_device_management
+echo "import ${MDM_PROFILE##*/} into your MDM to provision it"
+/usr/bin/open -R "$MDM_PROFILE"
+}
+
+_uninstall_launchagent() {
+ /bin/launchctl bootout gui/$UID "$AGENT_PLIST" 2>/dev/null
+ /bin/rm 2>/dev/null "$AGENT_PLIST"
+ echo "uninstalled $SELF LaunchAgent"
+}
+
+_install_launchagent() {
+ _uninstall_launchagent &>/dev/null
+ read -r FDA_TEST < <(/usr/bin/sqlite3 "$TCC_DB" <<-EOS
+ SELECT COUNT(client)
+ FROM access
+ WHERE
+ client = '/bin/bash' AND
+ service = 'kTCCServiceSystemPolicyAllFiles' AND
+ auth_value = 2
+ EOS
+ )
+ if (( FDA_TEST == 0 )); then
+ /bin/cat <<-EOF >&2
+ ┌──────────────────────────────────────────────────────────────────────────────────────┐
+ │ For the LaunchAgent to work properly, you must grant Full Disk Access to /bin/bash │
+ │ │
+ │ The Full Disk Access settings panel will now be opened. Press the (+) button near │
+ │ the bottom of the window, then press [⌘cmd + ⇧shift + g] and type '/bin/bash' and │
+ │ click Open to get it to appear in the app list. │
+ │ │
+ │ Once that's all done, run the --install command again. │
+ └──────────────────────────────────────────────────────────────────────────────────────┘
+ EOF
+ sleep 3
+ _fda_settings
+ return 1
+ fi
+ /bin/cat >"$AGENT_PLIST" <<-EOF
+
+
+
+
+ Label
+ $SELF.agent
+ ProgramArguments
+
+ /bin/bash
+ --norc
+ --noprofile
+ $FQPN
+
+ StandardErrorPath
+ /private/tmp/$SELF.stderr
+ StandardOutPath
+ /private/tmp/$SELF.stdout
+ StartInterval
+ $INTERVAL
+ WorkingDirectory
+ /private/tmp
+
+
+ EOF
+ /bin/chmod 644 "$PLIST"
+ if /bin/launchctl bootstrap gui/$UID "$AGENT_PLIST"; then
+ echo "installed $SELF LaunchAgent"
+ fi
+}
+
+_manual_add_desc() {
+ if _os_is_151_or_higher ; then
+ echo "-a,--add manually create an entry"
+ else
+ echo "-a,--add manually create an entry (supply full path to binary)"
+ fi
+}
+
+case $1 in
+ -h|--help)
+ /bin/cat <<-EOF
+
+ a tool to help suppress macOS Sequoia's persistent ScreenCapture alerts
+ usage: ${0##*/} [args]
+ -r,--reveal show ${PLIST##*/} in Finder
+ -p,--print print current values
+ $(_manual_add_desc)
+ --reset initialize empty ${PLIST##*/}
+ --generate_profile generate configuration profile for use with your MDM server
+ --profiles opens Device Management in System Settings
+ --install install LaunchAgent to ensure alerts continue to be silenced
+ --uninstall remove LaunchAgent
+ EOF
+ if _os_is_151_or_higher; then /bin/cat <<-EOF
+
+ ┌────────────────────────────────────────────────────────────────────────────────────┐
+ │ macOS 15.1 introduced an official method for suppressing ScreenCapture alerts │
+ │ for ALL apps on Macs enrolled in an MDM server (Jamf, Addigy, Mosyle etc). │
+ │ │
+ │ A configuration profile to enable this can be generated using --generate_profile │
+ └────────────────────────────────────────────────────────────────────────────────────┘
+
+ EOF
+ fi
+ exit
+ ;;
+ -r|--reveal)
+ if [[ -e $PLIST ]]; then
+ /usr/bin/open -R "$PLIST"
+ else
+ /usr/bin/open "$(/usr/bin/dirname "$PLIST")"
+ fi
+ exit
+ ;;
+ -p|--print)
+ if [[ -e $PLIST ]]; then
+ /usr/bin/plutil -p "$PLIST"
+ else
+ echo >&2 "${PLIST##*/} does not exist"
+ fi
+ exit
+ ;;
+ --reset) _create_plist || echo >&2 "error, could not create ${PLIST##*/}"; exit;;
+ --generate_profile) _generate_mdm_profile; exit;;
+ --profiles) _open_device_management; exit;;
+ --install) _install_launchagent; exit;;
+ --uninstall) _uninstall_launchagent; exit;;
+esac
+
+[[ -e $PLIST ]] || _create_plist
+if ! /usr/bin/touch "$PLIST" 2>/dev/null; then
+ if [[ -n $__CFBundleIdentifier ]]; then
+ TERMINAL_NAME=$(_bundleid_to_name "$__CFBundleIdentifier")
+ fi
+ echo >&2 "Full Disk Access permissions are missing${TERMINAL_NAME:+ for $TERMINAL_NAME}"
+ exit 1
+fi
+
+case $1 in
+ -a|--add)
+ _nagblock "$2"
+ _bounce_daemons
+ exit
+ ;;
+ -*) echo >&2 "invalid arg: $1"; exit 1;;
+esac
+
+c=0
+while read -r APP_PATH ; do
+ [[ -n $APP_PATH ]] || continue
+ _nagblock "$APP_PATH"
+done < <(_enum_apps)
+
+#bounce daemons if any changes were made so the new settings take effect
+(( c > 0 )) && _bounce_daemons
+
+exit 0
diff --git a/script/spec-runner.js b/script/spec-runner.js
index 3457fdb34a9..4c5e72acac0 100755
--- a/script/spec-runner.js
+++ b/script/spec-runner.js
@@ -2,6 +2,7 @@
const { ElectronVersions, Installer } = require('@electron/fiddle-core');
+const { DOMParser } = require('@xmldom/xmldom');
const chalk = require('chalk');
const { hashElement } = require('folder-hash');
const minimist = require('minimist');
@@ -21,6 +22,7 @@ const FAILURE_STATUS_KEY = 'Electron_Spec_Runner_Failures';
const args = minimist(process.argv, {
string: ['runners', 'target', 'electronVersion'],
+ number: ['enableRerun'],
unknown: arg => unknownFlags.push(arg)
});
@@ -191,7 +193,160 @@ async function asyncSpawn (exe, runnerArgs) {
});
}
-async function runTestUsingElectron (specDir, testName) {
+function parseJUnitXML (specDir) {
+ if (!fs.existsSync(process.env.MOCHA_FILE)) {
+ console.error('JUnit XML file not found:', process.env.MOCHA_FILE);
+ return [];
+ }
+
+ const xmlContent = fs.readFileSync(process.env.MOCHA_FILE, 'utf8');
+ const parser = new DOMParser();
+ const xmlDoc = parser.parseFromString(xmlContent, 'text/xml');
+
+ const failedTests = [];
+ // find failed tests by looking for all testsuite nodes with failure > 0
+ const testSuites = xmlDoc.getElementsByTagName('testsuite');
+ for (let i = 0; i < testSuites.length; i++) {
+ const testSuite = testSuites[i];
+ const failures = testSuite.getAttribute('failures');
+ if (failures > 0) {
+ const testcases = testSuite.getElementsByTagName('testcase');
+
+ for (let i = 0; i < testcases.length; i++) {
+ const testcase = testcases[i];
+ const failures = testcase.getElementsByTagName('failure');
+ const errors = testcase.getElementsByTagName('error');
+
+ if (failures.length > 0 || errors.length > 0) {
+ const testName = testcase.getAttribute('name');
+ const filePath = testSuite.getAttribute('file');
+ const fileName = filePath ? path.relative(specDir, filePath) : 'unknown file';
+ const failureInfo = {
+ name: testName,
+ file: fileName,
+ filePath
+ };
+ if (failures.length > 0) {
+ failureInfo.failure = failures[0].textContent || failures[0].nodeValue || 'No failure message';
+ }
+
+ if (errors.length > 0) {
+ failureInfo.error = errors[0].textContent || errors[0].nodeValue || 'No error message';
+ }
+
+ failedTests.push(failureInfo);
+ }
+ }
+ }
+ }
+
+ return failedTests;
+}
+
+async function rerunFailedTest (specDir, testName, testInfo) {
+ console.log('\n========================================');
+ console.log(`Rerunning failed test: ${testInfo.name} (${testInfo.file})`);
+ console.log('========================================');
+
+ let grepPattern = testInfo.name;
+
+ // Escape special regex characters in test name
+ grepPattern = grepPattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+
+ const args = [];
+ if (testInfo.filePath) {
+ args.push('--files', testInfo.filePath);
+ }
+ args.push('-g', grepPattern);
+
+ const success = await runTestUsingElectron(specDir, testName, false, args);
+
+ if (success) {
+ console.log(`✅ Test passed: ${testInfo.name}`);
+ return true;
+ } else {
+ console.log(`❌ Test failed again: ${testInfo.name}`);
+ return false;
+ }
+}
+
+async function rerunFailedTests (specDir, testName) {
+ console.log('\n📋 Parsing JUnit XML for failed tests...');
+ const failedTests = parseJUnitXML(specDir);
+
+ if (failedTests.length === 0) {
+ console.log('No failed tests could be found.');
+ process.exit(1);
+ return;
+ }
+
+ // Save off the original junit xml file
+ if (fs.existsSync(process.env.MOCHA_FILE)) {
+ fs.copyFileSync(process.env.MOCHA_FILE, `${process.env.MOCHA_FILE}.save`);
+ }
+
+ console.log(`\n📊 Found ${failedTests.length} failed test(s):`);
+ failedTests.forEach((test, index) => {
+ console.log(` ${index + 1}. ${test.name} (${test.file})`);
+ });
+
+ // Step 3: Rerun each failed test individually
+ console.log('\n🔄 Rerunning failed tests individually...\n');
+
+ const results = {
+ total: failedTests.length,
+ passed: 0,
+ failed: 0
+ };
+
+ let index = 0;
+ for (const testInfo of failedTests) {
+ let runCount = 0;
+ let success = false;
+ let retryTest = false;
+ while (!success && (runCount < args.enableRerun)) {
+ success = await rerunFailedTest(specDir, testName, testInfo);
+ if (success) {
+ results.passed++;
+ } else {
+ if (runCount === args.enableRerun - 1) {
+ results.failed++;
+ } else {
+ retryTest = true;
+ console.log(`Retrying test (${runCount + 1}/${args.enableRerun})...`);
+ }
+ }
+
+ // Add a small delay between tests
+ if (retryTest || index < failedTests.length - 1) {
+ console.log('\nWaiting 2 seconds before next test...');
+ await new Promise(resolve => setTimeout(resolve, 2000));
+ }
+ runCount++;
+ }
+ index++;
+ };
+
+ // Step 4: Summary
+ console.log('\n📈 Summary:');
+ console.log(`Total failed tests: ${results.total}`);
+ console.log(`Passed on rerun: ${results.passed}`);
+ console.log(`Still failing: ${results.failed}`);
+
+ // Restore the original junit xml file
+ if (fs.existsSync(`${process.env.MOCHA_FILE}.save`)) {
+ fs.renameSync(`${process.env.MOCHA_FILE}.save`, process.env.MOCHA_FILE);
+ }
+
+ if (results.failed === 0) {
+ console.log('🎉 All previously failed tests now pass!');
+ } else {
+ console.log(`⚠️ ${results.failed} test(s) are still failing`);
+ process.exit(1);
+ }
+}
+
+async function runTestUsingElectron (specDir, testName, shouldRerun, additionalArgs = []) {
let exe;
if (args.electronVersion) {
const installer = new Installer();
@@ -199,11 +354,16 @@ async function runTestUsingElectron (specDir, testName) {
} else {
exe = path.resolve(BASE, utils.getElectronExec());
}
- const runnerArgs = [`electron/${specDir}`, ...unknownArgs.slice(2)];
+ let argsToPass = unknownArgs.slice(2);
+ if (additionalArgs.includes('--files')) {
+ argsToPass = argsToPass.filter(arg => (arg.toString().indexOf('--files') === -1 && arg.toString().indexOf('spec/') === -1));
+ }
+ const runnerArgs = [`electron/${specDir}`, ...argsToPass, ...additionalArgs];
if (process.platform === 'linux') {
runnerArgs.unshift(path.resolve(__dirname, 'dbus_mock.py'), exe);
exe = 'python3';
}
+ console.log(`Running: ${exe} ${runnerArgs.join(' ')}`);
const { status, signal } = await asyncSpawn(exe, runnerArgs);
if (status !== 0) {
if (status) {
@@ -212,13 +372,22 @@ async function runTestUsingElectron (specDir, testName) {
} else {
console.log(`${fail} Electron tests failed with kill signal ${signal}.`);
}
- process.exit(1);
+ if (shouldRerun) {
+ await rerunFailedTests(specDir, testName);
+ } else {
+ return false;
+ }
}
console.log(`${pass} Electron ${testName} process tests passed.`);
+ return true;
}
async function runMainProcessElectronTests () {
- await runTestUsingElectron('spec', 'main');
+ let shouldRerun = false;
+ if (args.enableRerun && args.enableRerun > 0) {
+ shouldRerun = true;
+ }
+ await runTestUsingElectron('spec', 'main', shouldRerun);
}
async function installSpecModules (dir) {
diff --git a/spec/lib/screen-helpers.ts b/spec/lib/screen-helpers.ts
index ff68a5bb983..2358e5a35cd 100644
--- a/spec/lib/screen-helpers.ts
+++ b/spec/lib/screen-helpers.ts
@@ -78,13 +78,9 @@ function areColorsSimilar (
}
function displayCenter (display: Electron.Display): Electron.Point {
- // On macOS, we get system prompt to ask permission for screen capture
- // taking up space in the center. As a workaround, choose
- // area of the application window which is not covered by the prompt.
- // TODO: Remove this when the prompt situation is resolved.
return {
- x: display.size.width / (process.platform === 'darwin' ? 4 : 2),
- y: display.size.height / (process.platform === 'darwin' ? 4 : 2)
+ x: display.size.width / 2,
+ y: display.size.height / 2
};
}
diff --git a/spec/webview-spec.ts b/spec/webview-spec.ts
index a7e2989cd09..23d07e32569 100644
--- a/spec/webview-spec.ts
+++ b/spec/webview-spec.ts
@@ -1,4 +1,4 @@
-import { BrowserWindow, session, ipcMain, app, WebContents, screen } from 'electron/main';
+import { BrowserWindow, session, ipcMain, app, WebContents } from 'electron/main';
import * as auth from 'basic-auth';
import { expect } from 'chai';
@@ -782,7 +782,6 @@ describe(' tag', function () {
let w: BrowserWindow;
before(async () => {
- const display = screen.getPrimaryDisplay();
w = new BrowserWindow({
webPreferences: {
webviewTag: true,
@@ -790,7 +789,6 @@ describe(' tag', function () {
contextIsolation: false
}
});
- w.setBounds(display.bounds);
await w.loadURL(`file://${fixtures}/pages/flex-webview.html`);
w.setBackgroundColor(WINDOW_BACKGROUND_COLOR);
});
diff --git a/yarn.lock b/yarn.lock
index c995f4b7fd8..bd5c1017a43 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1345,6 +1345,11 @@
resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.5.tgz#325db42395cd49fe6c14057f9a900e427df8810e"
integrity sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==
+"@xmldom/xmldom@^0.8.11":
+ version "0.8.11"
+ resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.11.tgz#b79de2d67389734c57c52595f7a7305e30c2d608"
+ integrity sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==
+
"@xtuc/ieee754@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
@@ -7347,14 +7352,7 @@ stringify-entities@^4.0.0:
character-entities-html4 "^2.0.0"
character-entities-legacy "^3.0.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
- version "6.0.1"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
- integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
- dependencies:
- ansi-regex "^5.0.1"
-
-strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==