From 123e46c33b1544f39ab26bb1c3240b13861d46ec Mon Sep 17 00:00:00 2001 From: Paul Marbach Date: Wed, 22 Oct 2025 15:09:39 -0400 Subject: [PATCH] Coverage: Add some DX improvements to by codeowner script (#112673) * Coverage: Add some DX improvements to by codeowner script * Potential fix for code scanning alert no. 3796: Shell command built from environment values Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Potential fix for code scanning alert no. 3797: Shell command built from environment values Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * fix package.json and yarn lock * reorder imports * fix issue for frontend-platform: exclude files in any /test/ dir * wip * add ora spinner for codeowners manifest step * cleanup --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- jest.config.codeowner.js | 44 +++++-- package.json | 5 +- scripts/codeowners-manifest/index.js | 11 +- scripts/codeowners-manifest/utils.js | 34 +++++ scripts/test-coverage-by-codeowner.js | 65 ++++++---- yarn.lock | 179 +++++++++++++++++++++++++- 6 files changed, 298 insertions(+), 40 deletions(-) create mode 100644 scripts/codeowners-manifest/utils.js diff --git a/jest.config.codeowner.js b/jest.config.codeowner.js index d94df3cfa3e..5e1a2755949 100644 --- a/jest.config.codeowner.js +++ b/jest.config.codeowner.js @@ -1,17 +1,18 @@ const fs = require('fs'); +const open = require('open').default; const path = require('path'); const baseConfig = require('./jest.config.js'); const CODEOWNERS_MANIFEST_FILENAMES_BY_TEAM_PATH = 'codeowners-manifest/filenames-by-team.json'; -const teamName = process.env.TEAM_NAME; -if (!teamName) { - console.error('ERROR: TEAM_NAME environment variable is required'); +const codeownerName = process.env.CODEOWNER_NAME; +if (!codeownerName) { + console.error('ERROR: CODEOWNER_NAME environment variable is required'); process.exit(1); } -const outputDir = `./coverage/by-team/${createOwnerDirectory(teamName)}`; +const outputDir = `./coverage/by-team/${createOwnerDirectory(codeownerName)}`; const codeownersFilePath = path.join(__dirname, CODEOWNERS_MANIFEST_FILENAMES_BY_TEAM_PATH); @@ -22,10 +23,10 @@ if (!fs.existsSync(codeownersFilePath)) { } const codeownersData = JSON.parse(fs.readFileSync(codeownersFilePath, 'utf8')); -const teamFiles = codeownersData[teamName] || []; +const teamFiles = codeownersData[codeownerName] || []; if (teamFiles.length === 0) { - console.error(`ERROR: No files found for team "${teamName}"`); + console.error(`ERROR: No files found for team "${codeownerName}"`); console.error('Available teams:', Object.keys(codeownersData).join(', ')); process.exit(1); } @@ -34,10 +35,15 @@ const sourceFiles = teamFiles.filter((file) => { const ext = path.extname(file); return ( ['.ts', '.tsx', '.js', '.jsx'].includes(ext) && + // exclude all tests + !path.matchesGlob(file, '**/test/**/*') && !file.includes('.test.') && !file.includes('.spec.') && + // and storybook stories !file.includes('.story.') && + // and generated files !file.includes('.gen.ts') && + // and type definitions !file.includes('.d.ts') && !file.endsWith('/types.ts') ); @@ -49,12 +55,12 @@ const testFiles = teamFiles.filter((file) => { }); if (testFiles.length === 0) { - console.log(`No test files found for team ${teamName}`); + console.log(`No test files found for team ${codeownerName}`); process.exit(0); } console.log( - `๐Ÿงช Collecting coverage for ${sourceFiles.length} testable files and running ${testFiles.length} test files of ${teamFiles.length} files owned by ${teamName}.` + `๐Ÿงช Collecting coverage for ${sourceFiles.length} testable files and running ${testFiles.length} test files of ${teamFiles.length} files owned by ${codeownerName}.` ); module.exports = { @@ -71,7 +77,7 @@ module.exports = { [ 'jest-monocart-coverage', { - name: `Coverage Report - ${teamName} owned files`, + name: `Coverage Report - ${codeownerName} owned files`, outputDir: outputDir, reports: ['console-summary', 'v8', 'json', 'lcov'], sourceFilter: (coveredFile) => sourceFiles.includes(coveredFile), @@ -84,7 +90,13 @@ module.exports = { }, cleanCache: true, onEnd: (coverageResults) => { - console.log(`๐Ÿ“„ Coverage report saved to file://${path.resolve(outputDir)}/index.html`); + const reportURL = `file://${path.resolve(outputDir)}/index.html`; + console.log(`๐Ÿ“„ Coverage report saved to ${reportURL}`); + + if (process.env.SHOULD_OPEN_COVERAGE_REPORT === 'true') { + openCoverageReport(reportURL); + } + // TODO: Emit coverage metrics https://github.com/grafana/grafana/issues/111208 }, }, @@ -115,3 +127,15 @@ function createOwnerDirectory(owner) { return `emails/${user}-at-${domain}`; } } + +/** + * Open the given file URL in the default browser safely, without shell injection risk. + * @param {string} reportURL + */ +async function openCoverageReport(reportURL) { + try { + await open(reportURL); + } catch (err) { + console.error(`Failed to open coverage report: ${err}`); + } +} diff --git a/package.json b/package.json index d045ebb590a..cde5b379137 100644 --- a/package.json +++ b/package.json @@ -36,8 +36,8 @@ "test": "jest --notify --watch", "test:coverage": "jest --coverage", "test:coverage:by-codeowner": "yarn codeowners-manifest && node scripts/test-coverage-by-codeowner.js", - "test:storybook": "yarn workspace @grafana/ui storybook:test", "test:coverage:changes": "jest --coverage --changedSince=origin/main", + "test:storybook": "yarn workspace @grafana/ui storybook:test", "test:accessibility-report": "./scripts/generate-a11y-report.sh", "test:ci": "mkdir -p reports/junit && JEST_JUNIT_OUTPUT_DIR=reports/junit jest --ci --reporters=default --reporters=jest-junit -w ${TEST_MAX_WORKERS:-100%} --shard=${TEST_SHARD:-1}/${TEST_SHARD_TOTAL:-1}", "lint": "yarn run lint:ts && yarn run lint:sass", @@ -183,6 +183,7 @@ "cypress": "14.3.2", "cypress-file-upload": "5.0.8", "cypress-recurse": "^1.35.3", + "enquirer": "^2.4.1", "esbuild": "0.25.8", "esbuild-loader": "4.3.0", "esbuild-plugin-browserslist": "^1.0.0", @@ -229,7 +230,9 @@ "mutationobserver-shim": "0.3.7", "node-notifier": "10.0.1", "nx": "21.3.11", + "open": "^10.2.0", "openapi-types": "^12.1.3", + "ora": "^9.0.0", "pa11y-ci": "^4.0.0", "pdf-parse": "^1.1.1", "plop": "^4.0.1", diff --git a/scripts/codeowners-manifest/index.js b/scripts/codeowners-manifest/index.js index 4105fc90520..3e56038941b 100755 --- a/scripts/codeowners-manifest/index.js +++ b/scripts/codeowners-manifest/index.js @@ -1,6 +1,7 @@ #!/usr/bin/env node const { writeFile, readFile, mkdir, access } = require('node:fs/promises'); +const ora = require('ora').default; const { CODEOWNERS_FILE_PATH, @@ -72,9 +73,8 @@ async function generateCodeownersManifestComplete( if (require.main === module) { (async () => { + const spinner = ora('Generating complete codeowners manifest...').start(); try { - console.log('๐Ÿ“‹ Generating complete codeowners manifest...'); - const wasGenerated = await generateCodeownersManifestComplete( CODEOWNERS_FILE_PATH, CODEOWNERS_MANIFEST_DIR, @@ -86,13 +86,12 @@ if (require.main === module) { ); if (wasGenerated) { - console.log('โœ… Complete manifest generated:'); - console.log(` โ€ข ${CODEOWNERS_MANIFEST_DIR}/`); + spinner.succeed(`โœ… Complete manifest generated: ${CODEOWNERS_MANIFEST_DIR}/`); } else { - console.log('โœ… Manifest up-to-date, skipped generation'); + spinner.succeed('โœ… Manifest up-to-date, skipped generation'); } } catch (e) { - console.error('โŒ Error generating codeowners manifest:', e.message); + spinner.fail(`โŒ Error generating codeowners manifest: ${e.message}`); process.exit(1); } })(); diff --git a/scripts/codeowners-manifest/utils.js b/scripts/codeowners-manifest/utils.js new file mode 100644 index 00000000000..ac9d81763a5 --- /dev/null +++ b/scripts/codeowners-manifest/utils.js @@ -0,0 +1,34 @@ +const { readFile } = require('node:fs/promises'); + +const { CODEOWNERS_JSON_PATH: CODEOWNERS_MANIFEST_CODEOWNERS_PATH } = require('./constants.js'); + +let _codeownersCache = null; + +module.exports = { + /** + * import the contents of the codeowners manifest JSON file, with caching + * @param {boolean} clearCache - if true, clear the cached data and reload the codeowners manifest + * @returns {Promise>} - list of codeowners which own at least one file in the project + */ + async getCodeowners(clearCache = false) { + if (clearCache) { + _codeownersCache = null; + } + + if (_codeownersCache == null) { + try { + const codeownersJson = await readFile(CODEOWNERS_MANIFEST_CODEOWNERS_PATH, 'utf8'); + _codeownersCache = JSON.parse(codeownersJson); + } catch (e) { + if (e.code === 'ENOENT') { + console.error(`Could not read ${CODEOWNERS_MANIFEST_CODEOWNERS_PATH} ...`); + } else { + console.error(e); + } + process.exit(1); + } + } + + return _codeownersCache; + }, +}; diff --git a/scripts/test-coverage-by-codeowner.js b/scripts/test-coverage-by-codeowner.js index e2741846456..55c13dfa6e9 100644 --- a/scripts/test-coverage-by-codeowner.js +++ b/scripts/test-coverage-by-codeowner.js @@ -1,30 +1,44 @@ #!/usr/bin/env node +const { AutoComplete } = require('enquirer'); const cp = require('node:child_process'); -const { readFile } = require('node:fs/promises'); +const { hideBin } = require('yargs/helpers'); +const yargs = require('yargs/yargs'); -const { CODEOWNERS_JSON_PATH: CODEOWNERS_MANIFEST_CODEOWNERS_PATH } = require('./codeowners-manifest/constants.js'); +const { getCodeowners } = require('./codeowners-manifest/utils.js'); const JEST_CONFIG_PATH = 'jest.config.codeowner.js'; +async function promptCodeownerName() { + const teams = await getCodeowners(); + const prompt = new AutoComplete({ + name: 'flavor', + message: 'Select your team to run tests by codeowner.', + limit: 10, + choices: teams.filter((team) => team.startsWith('@grafana/')), + }); + return await prompt.run(); +} + /** * Run test coverage for a specific codeowner * @param {string} codeownerName - The codeowner name to run coverage for - * @param {string} codeownersPath - Path to the teams.json file - * @param {string} jestConfigPath - Path to the Jest config file + * @param {boolean} noOpen - Whether to skip opening the coverage report in the browser */ -async function runTestCoverageByCodeowner(codeownerName, codeownersPath, jestConfigPath) { - const codeownersJson = await readFile(codeownersPath, 'utf8'); - const codeowners = JSON.parse(codeownersJson); - - if (!codeowners.includes(codeownerName)) { - throw new Error(`Codeowner ${codeownerName} was not found in ${codeownersPath}, check spelling`); +async function runTestCoverageByCodeowner(codeownerName, noOpen = process.env.CI === 'true') { + const teams = await getCodeowners(); + if (!teams.includes(codeownerName)) { + throw new Error(`Codeowner "${codeownerName}" was not found.`); } - process.env.TEAM_NAME = codeownerName; + process.env.CODEOWNER_NAME = codeownerName; + process.env.SHOULD_OPEN_COVERAGE_REPORT = String(!noOpen); return new Promise((resolve, reject) => { - const child = cp.spawn('jest', [`--config=${jestConfigPath}`], { stdio: 'inherit', shell: true }); + const child = cp.spawn('jest', [`--config=${JEST_CONFIG_PATH}`], { + stdio: 'inherit', + shell: true, + }); child.on('error', (error) => { reject(new Error(`Failed to start Jest: ${error.message}`)); @@ -43,22 +57,29 @@ async function runTestCoverageByCodeowner(codeownerName, codeownersPath, jestCon if (require.main === module) { (async () => { try { - const codeownerName = process.argv[2]; + const argv = yargs(hideBin(process.argv)).parse(); + const teams = await getCodeowners(); + let codeownerName = argv._[0]; + if (codeownerName != null && !teams.includes(codeownerName)) { + const msg = `Codeowner "${codeownerName}" was not found.`; + codeownerName = null; + if (process.env.CI === 'true') { + throw new Error(msg); + } else { + console.warn(`โš ๏ธ ${msg}`); + } + } if (!codeownerName) { - console.error('Codeowner argument is required ...'); - console.error('Usage: yarn test:coverage:by-codeowner @grafana/team-name'); - process.exit(1); + codeownerName = await promptCodeownerName(); } + const noOpen = argv['open'] === false; + console.log(`๐Ÿงช Running test coverage for codeowner: ${codeownerName}`); - await runTestCoverageByCodeowner(codeownerName, CODEOWNERS_MANIFEST_CODEOWNERS_PATH, JEST_CONFIG_PATH); + await runTestCoverageByCodeowner(codeownerName, noOpen); } catch (e) { - if (e.code === 'ENOENT') { - console.error(`Could not read ${CODEOWNERS_MANIFEST_CODEOWNERS_PATH} ...`); - } else { - console.error(e.message); - } + console.error(e.message); process.exit(1); } })(); diff --git a/yarn.lock b/yarn.lock index 2ef46528754..25678c12d2d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12506,6 +12506,15 @@ __metadata: languageName: node linkType: hard +"bundle-name@npm:^4.1.0": + version: 4.1.0 + resolution: "bundle-name@npm:4.1.0" + dependencies: + run-applescript: "npm:^7.0.0" + checksum: 10/1d966c8d2dbf4d9d394e53b724ac756c2414c45c01340b37743621f59cc565a435024b394ddcb62b9b335d1c9a31f4640eb648c3fec7f97ee74dc0694c9beb6c + languageName: node + linkType: hard + "byte-size@npm:8.1.1": version: 8.1.1 resolution: "byte-size@npm:8.1.1" @@ -12812,6 +12821,13 @@ __metadata: languageName: node linkType: hard +"chalk@npm:^5.6.2": + version: 5.6.2 + resolution: "chalk@npm:5.6.2" + checksum: 10/1b2f48f6fba1370670d5610f9cd54c391d6ede28f4b7062dd38244ea5768777af72e5be6b74fb6c6d54cb84c4a2dff3f3afa9b7cb5948f7f022cfd3d087989e0 + languageName: node + linkType: hard + "chance@npm:^1.1.13": version: 1.1.13 resolution: "chance@npm:1.1.13" @@ -13118,6 +13134,13 @@ __metadata: languageName: node linkType: hard +"cli-spinners@npm:^3.2.0": + version: 3.3.0 + resolution: "cli-spinners@npm:3.3.0" + checksum: 10/d95f69f4a6a4efab2104ca5d4723c9f6fae9a4006df7fdcc1f79ea6539324e274b85bf6f5931146d84296b0f71814f4c1ff1acc158f2e1107c0c9797c1291bcc + languageName: node + linkType: hard + "cli-table3@npm:^0.6.3, cli-table3@npm:^0.6.5, cli-table3@npm:~0.6.5": version: 0.6.5 resolution: "cli-table3@npm:0.6.5" @@ -14995,6 +15018,23 @@ __metadata: languageName: node linkType: hard +"default-browser-id@npm:^5.0.0": + version: 5.0.0 + resolution: "default-browser-id@npm:5.0.0" + checksum: 10/185bfaecec2c75fa423544af722a3469b20704c8d1942794a86e4364fe7d9e8e9f63241a5b769d61c8151993bc65833a5b959026fa1ccea343b3db0a33aa6deb + languageName: node + linkType: hard + +"default-browser@npm:^5.2.1": + version: 5.2.1 + resolution: "default-browser@npm:5.2.1" + dependencies: + bundle-name: "npm:^4.1.0" + default-browser-id: "npm:^5.0.0" + checksum: 10/afab7eff7b7f5f7a94d9114d1ec67273d3fbc539edf8c0f80019879d53aa71e867303c6f6d7cffeb10a6f3cfb59d4f963dba3f9c96830b4540cc7339a1bf9840 + languageName: node + linkType: hard + "default-require-extensions@npm:^3.0.0": version: 3.0.1 resolution: "default-require-extensions@npm:3.0.1" @@ -15031,6 +15071,13 @@ __metadata: languageName: node linkType: hard +"define-lazy-prop@npm:^3.0.0": + version: 3.0.0 + resolution: "define-lazy-prop@npm:3.0.0" + checksum: 10/f28421cf9ee86eecaf5f3b8fe875f13d7009c2625e97645bfff7a2a49aca678270b86c39f9c32939e5ca7ab96b551377ed4139558c795e076774287ad3af1aa4 + languageName: node + linkType: hard + "define-properties@npm:^1.1.3, define-properties@npm:^1.2.1": version: 1.2.1 resolution: "define-properties@npm:1.2.1" @@ -15701,6 +15748,16 @@ __metadata: languageName: node linkType: hard +"enquirer@npm:^2.4.1": + version: 2.4.1 + resolution: "enquirer@npm:2.4.1" + dependencies: + ansi-colors: "npm:^4.1.1" + strip-ansi: "npm:^6.0.1" + checksum: 10/b3726486cd98f0d458a851a03326a2a5dd4d84f37ff94ff2a2960c915e0fc865865da3b78f0877dc36ac5c1189069eca603e82ec63d5bc6b0dd9985bf6426d7a + languageName: node + linkType: hard + "ensure-posix-path@npm:^1.1.0": version: 1.1.1 resolution: "ensure-posix-path@npm:1.1.1" @@ -17789,6 +17846,13 @@ __metadata: languageName: node linkType: hard +"get-east-asian-width@npm:^1.3.0": + version: 1.4.0 + resolution: "get-east-asian-width@npm:1.4.0" + checksum: 10/c9ae85bfc2feaf4cc71cdb236e60f1757ae82281964c206c6aa89a25f1987d326ddd8b0de9f9ccd56e37711b9fcd988f7f5137118b49b0b45e19df93c3be8f45 + languageName: node + linkType: hard + "get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.2.7, get-intrinsic@npm:^1.3.0": version: 1.3.0 resolution: "get-intrinsic@npm:1.3.0" @@ -18483,6 +18547,7 @@ __metadata: date-fns: "npm:4.1.0" debounce-promise: "npm:3.1.2" diff: "npm:^8.0.0" + enquirer: "npm:^2.4.1" esbuild: "npm:0.25.8" esbuild-loader: "npm:4.3.0" esbuild-plugin-browserslist: "npm:^1.0.0" @@ -18566,7 +18631,9 @@ __metadata: nx: "npm:21.3.11" ol: "npm:10.6.1" ol-ext: "npm:4.0.35" + open: "npm:^10.2.0" openapi-types: "npm:^12.1.3" + ora: "npm:^9.0.0" pa11y-ci: "npm:^4.0.0" pdf-parse: "npm:^1.1.1" plop: "npm:^4.0.1" @@ -20003,6 +20070,15 @@ __metadata: languageName: node linkType: hard +"is-docker@npm:^3.0.0": + version: 3.0.0 + resolution: "is-docker@npm:3.0.0" + bin: + is-docker: cli.js + checksum: 10/b698118f04feb7eaf3338922bd79cba064ea54a1c3db6ec8c0c8d8ee7613e7e5854d802d3ef646812a8a3ace81182a085dfa0a71cc68b06f3fa794b9783b3c90 + languageName: node + linkType: hard + "is-extendable@npm:^1.0.0": version: 1.0.1 resolution: "is-extendable@npm:1.0.1" @@ -20097,6 +20173,17 @@ __metadata: languageName: node linkType: hard +"is-inside-container@npm:^1.0.0": + version: 1.0.0 + resolution: "is-inside-container@npm:1.0.0" + dependencies: + is-docker: "npm:^3.0.0" + bin: + is-inside-container: cli.js + checksum: 10/c50b75a2ab66ab3e8b92b3bc534e1ea72ca25766832c0623ac22d134116a98bcf012197d1caabe1d1c4bd5f84363d4aa5c36bb4b585fbcaf57be172cd10a1a03 + languageName: node + linkType: hard + "is-installed-globally@npm:~0.4.0": version: 0.4.0 resolution: "is-installed-globally@npm:0.4.0" @@ -20398,7 +20485,7 @@ __metadata: languageName: node linkType: hard -"is-unicode-supported@npm:^2.0.0": +"is-unicode-supported@npm:^2.0.0, is-unicode-supported@npm:^2.1.0": version: 2.1.0 resolution: "is-unicode-supported@npm:2.1.0" checksum: 10/f254e3da6b0ab1a57a94f7273a7798dd35d1d45b227759f600d0fa9d5649f9c07fa8d3c8a6360b0e376adf916d151ec24fc9a50c5295c58bae7ca54a76a063f9 @@ -20468,6 +20555,15 @@ __metadata: languageName: node linkType: hard +"is-wsl@npm:^3.1.0": + version: 3.1.0 + resolution: "is-wsl@npm:3.1.0" + dependencies: + is-inside-container: "npm:^1.0.0" + checksum: 10/f9734c81f2f9cf9877c5db8356bfe1ff61680f1f4c1011e91278a9c0564b395ae796addb4bf33956871041476ec82c3e5260ed57b22ac91794d4ae70a1d2f0a9 + languageName: node + linkType: hard + "is@npm:^3.3.0": version: 3.3.0 resolution: "is@npm:3.3.0" @@ -22396,6 +22492,16 @@ __metadata: languageName: node linkType: hard +"log-symbols@npm:^7.0.1": + version: 7.0.1 + resolution: "log-symbols@npm:7.0.1" + dependencies: + is-unicode-supported: "npm:^2.0.0" + yoctocolors: "npm:^2.1.1" + checksum: 10/0862313d84826b551582e39659b8586c56b65130c5f4f976420e2c23985228334f2a26fc4251ac22bf0a5b415d9430e86bf332557d934c10b036f9a549d63a09 + languageName: node + linkType: hard + "log-update@npm:^4.0.0": version: 4.0.0 resolution: "log-update@npm:4.0.0" @@ -24663,6 +24769,18 @@ __metadata: languageName: node linkType: hard +"open@npm:^10.2.0": + version: 10.2.0 + resolution: "open@npm:10.2.0" + dependencies: + default-browser: "npm:^5.2.1" + define-lazy-prop: "npm:^3.0.0" + is-inside-container: "npm:^1.0.0" + wsl-utils: "npm:^0.1.0" + checksum: 10/e6ad9474734eac3549dcc7d85e952394856ccaee48107c453bd6a725b82e3b8ed5f427658935df27efa76b411aeef62888edea8a9e347e8e7c82632ec966b30e + languageName: node + linkType: hard + "open@npm:^8.0.4, open@npm:^8.4.0, open@npm:^8.4.2": version: 8.4.2 resolution: "open@npm:8.4.2" @@ -24786,6 +24904,23 @@ __metadata: languageName: node linkType: hard +"ora@npm:^9.0.0": + version: 9.0.0 + resolution: "ora@npm:9.0.0" + dependencies: + chalk: "npm:^5.6.2" + cli-cursor: "npm:^5.0.0" + cli-spinners: "npm:^3.2.0" + is-interactive: "npm:^2.0.0" + is-unicode-supported: "npm:^2.1.0" + log-symbols: "npm:^7.0.1" + stdin-discarder: "npm:^0.2.2" + string-width: "npm:^8.1.0" + strip-ansi: "npm:^7.1.2" + checksum: 10/b6074c9cec4a39c1b4f41c2ce2741982a99c53c86bd6f07a28fb6274857263af7fe1a340136629939934b553af35b03fc62ca2a88baa6803b2f9bfdf269fb850 + languageName: node + linkType: hard + "os-homedir@npm:^1.0.1": version: 1.0.2 resolution: "os-homedir@npm:1.0.2" @@ -28759,6 +28894,13 @@ __metadata: languageName: node linkType: hard +"run-applescript@npm:^7.0.0": + version: 7.1.0 + resolution: "run-applescript@npm:7.1.0" + checksum: 10/8659fb5f2717b2b37a68cbfe5f678254cf24b5a82a6df3372b180c80c7c137dcd757a4166c3887e459f59a090ca414e8ea7ca97cf3ee5123db54b3b4006d7b7a + languageName: node + linkType: hard + "run-async@npm:^2.4.0": version: 2.4.1 resolution: "run-async@npm:2.4.1" @@ -30188,6 +30330,16 @@ __metadata: languageName: node linkType: hard +"string-width@npm:^8.1.0": + version: 8.1.0 + resolution: "string-width@npm:8.1.0" + dependencies: + get-east-asian-width: "npm:^1.3.0" + strip-ansi: "npm:^7.1.0" + checksum: 10/51ee97c4ffee7b94f8a2ee785fac14f81ec9809b9fcec9a4db44e25c717c263af0cc4387c111aef76195c0718dc43766f3678c07fb542294fb0244f7bfbde883 + languageName: node + linkType: hard + "string.prototype.includes@npm:^2.0.1": version: 2.0.1 resolution: "string.prototype.includes@npm:2.0.1" @@ -30320,6 +30472,15 @@ __metadata: languageName: node linkType: hard +"strip-ansi@npm:^7.1.2": + version: 7.1.2 + resolution: "strip-ansi@npm:7.1.2" + dependencies: + ansi-regex: "npm:^6.0.1" + checksum: 10/db0e3f9654e519c8a33c50fc9304d07df5649388e7da06d3aabf66d29e5ad65d5e6315d8519d409c15b32fa82c1df7e11ed6f8cd50b0e4404463f0c9d77c8d0b + languageName: node + linkType: hard + "strip-bom@npm:^3.0.0": version: 3.0.0 resolution: "strip-bom@npm:3.0.0" @@ -33245,6 +33406,15 @@ __metadata: languageName: node linkType: hard +"wsl-utils@npm:^0.1.0": + version: 0.1.0 + resolution: "wsl-utils@npm:0.1.0" + dependencies: + is-wsl: "npm:^3.1.0" + checksum: 10/de4c92187e04c3c27b4478f410a02e81c351dc85efa3447bf1666f34fc80baacd890a6698ec91995631714086992036013286aea3d77e6974020d40a08e00aec + languageName: node + linkType: hard + "xml-but-prettier@npm:^1.0.1": version: 1.0.1 resolution: "xml-but-prettier@npm:1.0.1" @@ -33503,6 +33673,13 @@ __metadata: languageName: node linkType: hard +"yoctocolors@npm:^2.1.1": + version: 2.1.2 + resolution: "yoctocolors@npm:2.1.2" + checksum: 10/6ee42d665a4cc161c7de3f015b2a65d6c65d2808bfe3b99e228bd2b1b784ef1e54d1907415c025fc12b400f26f372bfc1b71966c6c738d998325ca422eb39363 + languageName: node + linkType: hard + "zenscroll@npm:^4.0.2": version: 4.0.2 resolution: "zenscroll@npm:4.0.2"