From add5de6478d98bbbb0e5e27e3a49148b67add17f Mon Sep 17 00:00:00 2001 From: syn-zhu <167124917+syn-zhu@users.noreply.github.com> Date: Tue, 22 Oct 2024 12:04:10 -0400 Subject: [PATCH 1/6] chore(e2e): enable e2e tests to run directly against Atlas (#6381) * enable test against atlas * use const * update function * comments * fix if check * comments * comments --- .../helpers/commands/connect-form.ts | 7 + packages/compass-e2e-tests/helpers/compass.ts | 77 +++++++-- packages/compass-e2e-tests/index.ts | 154 ++++++++++-------- 3 files changed, 156 insertions(+), 82 deletions(-) diff --git a/packages/compass-e2e-tests/helpers/commands/connect-form.ts b/packages/compass-e2e-tests/helpers/commands/connect-form.ts index 97575d39fce..d6a11d99bbd 100644 --- a/packages/compass-e2e-tests/helpers/commands/connect-form.ts +++ b/packages/compass-e2e-tests/helpers/commands/connect-form.ts @@ -8,6 +8,7 @@ import { DEFAULT_CONNECTION_NAME_2, DEFAULT_CONNECTION_STRING_1, DEFAULT_CONNECTION_STRING_2, + TEST_ATLAS_CLOUD_EXTERNAL_URL, TEST_MULTIPLE_CONNECTIONS, } from '../compass'; import Debug from 'debug'; @@ -986,6 +987,12 @@ export async function setupDefaultConnections(browser: CompassBrowser) { whereas we do have some tests that try and use those. We can easily change this in future if needed, though. */ + + // no need to setup connections if we are running against Atlas + if (TEST_ATLAS_CLOUD_EXTERNAL_URL) { + return; + } + for (const connectionName of [ DEFAULT_CONNECTION_NAME_1, DEFAULT_CONNECTION_NAME_2, diff --git a/packages/compass-e2e-tests/helpers/compass.ts b/packages/compass-e2e-tests/helpers/compass.ts index 3cd10cc47a0..d20366cc403 100644 --- a/packages/compass-e2e-tests/helpers/compass.ts +++ b/packages/compass-e2e-tests/helpers/compass.ts @@ -46,6 +46,10 @@ let MONGODB_USE_ENTERPRISE = // should we test compass-web (true) or compass electron (false)? export const TEST_COMPASS_WEB = process.argv.includes('--test-compass-web'); +export const TEST_ATLAS_CLOUD_EXTERNAL_URL = + process.env.TEST_ATLAS_CLOUD_EXTERNAL_URL; +export const TEST_ATLAS_CLOUD_EXTERNAL_GROUP_ID = + process.env.TEST_ATLAS_CLOUD_EXTERNAL_GROUP_ID; // multiple connections is now the default export const TEST_MULTIPLE_CONNECTIONS = true; @@ -75,19 +79,22 @@ export const MONGODB_TEST_SERVER_PORT = Number( process.env.MONGODB_TEST_SERVER_PORT ?? 27091 ); -export const DEFAULT_CONNECTION_STRING_1 = `mongodb://127.0.0.1:${MONGODB_TEST_SERVER_PORT}/test`; +export const DEFAULT_CONNECTION_STRING_1 = + process.env.TEST_ATLAS_CLOUD_EXTERNAL_CONNECTION_STRING_1 || + `mongodb://127.0.0.1:${MONGODB_TEST_SERVER_PORT}/test`; // NOTE: in browser.setupDefaultConnections() we don't give the first connection an // explicit name, so it gets a calculated one based off the connection string -export const DEFAULT_CONNECTION_NAME_1 = connectionNameFromString( - DEFAULT_CONNECTION_STRING_1 -); +export const DEFAULT_CONNECTION_NAME_1 = + process.env.TEST_ATLAS_CLOUD_EXTERNAL_CONNECTION_NAME_1 || + connectionNameFromString(DEFAULT_CONNECTION_STRING_1); // for testing multiple connections -export const DEFAULT_CONNECTION_STRING_2 = `mongodb://127.0.0.1:${ - MONGODB_TEST_SERVER_PORT + 1 -}/test`; +export const DEFAULT_CONNECTION_STRING_2 = + process.env.TEST_ATLAS_CLOUD_EXTERNAL_CONNECTION_STRING_2 || + `mongodb://127.0.0.1:${MONGODB_TEST_SERVER_PORT + 1}/test`; // NOTE: in browser.setupDefaultConnections() the second connection gets given an explicit name -export const DEFAULT_CONNECTION_NAME_2 = 'connection-2'; +export const DEFAULT_CONNECTION_NAME_2 = + process.env.TEST_ATLAS_CLOUD_EXTERNAL_CONNECTION_NAME_2 || 'connection-2'; export function updateMongoDBServerInfo() { try { @@ -106,7 +113,7 @@ export function updateMongoDBServerInfo() { 'server-info', '--', '--connectionString', - `mongodb://127.0.0.1:${String(MONGODB_TEST_SERVER_PORT)}`, + DEFAULT_CONNECTION_STRING_1, ], { encoding: 'utf-8' } ); @@ -761,6 +768,16 @@ async function startCompassElectron( return compass; } +export type StoredAtlasCloudCookies = { + name: string; + value: string; + domain: string; + path: string; + secure: boolean; + httpOnly: boolean; + expirationDate: number; +}[]; + export async function startBrowser( name: string, // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -787,7 +804,47 @@ export async function startBrowser( ...webdriverOptions, ...wdioOptions, })) as CompassBrowser; - await browser.navigateTo('http://localhost:7777/'); + + if (TEST_ATLAS_CLOUD_EXTERNAL_URL) { + // Navigate to a 404 page to set cookies + await browser.navigateTo(`https://${TEST_ATLAS_CLOUD_EXTERNAL_URL}/404`); + + const cookiesFile = process.env.TEST_ATLAS_CLOUD_EXTERNAL_COOKIES_FILE; + if (!cookiesFile) { + throw new Error( + 'TEST_ATLAS_CLOUD_EXTERNAL_URL is set but TEST_ATLAS_CLOUD_EXTERNAL_COOKIES_FILE is not. Please set TEST_ATLAS_CLOUD_EXTERNAL_COOKIES_FILE to the path of the cookies file.' + ); + } + const cookies: StoredAtlasCloudCookies = JSON.parse( + await fs.readFile(cookiesFile, 'utf8') + ); + + // These are the relevant cookies for auth: + // https://github.com/10gen/mms/blob/6d27992a6ab9ab31471c8bcdaa4e347aa39f4013/server/src/features/com/xgen/svc/cukes/helpers/Client.java#L122-L130 + await browser.setCookies( + cookies + .filter((cookie) => { + cookie.name.includes('mmsa-') || + cookie.name.includes('mdb-sat') || + cookie.name.includes('mdb-srt'); + }) + .map((cookie) => ({ + name: cookie.name, + value: cookie.value, + domain: cookie.domain, + path: cookie.path, + secure: cookie.secure, + httpOnly: cookie.httpOnly, + })) + ); + + await browser.navigateTo( + `https://${TEST_ATLAS_CLOUD_EXTERNAL_URL}/v2/${TEST_ATLAS_CLOUD_EXTERNAL_GROUP_ID}#/explorer` + ); + } else { + await browser.navigateTo('http://localhost:7777/'); + } + const compass = new Compass(name, browser, { mode: 'web', writeCoverage: false, diff --git a/packages/compass-e2e-tests/index.ts b/packages/compass-e2e-tests/index.ts index 309767330a2..0974b80e50c 100644 --- a/packages/compass-e2e-tests/index.ts +++ b/packages/compass-e2e-tests/index.ts @@ -18,6 +18,7 @@ import { LOG_PATH, removeUserDataDir, updateMongoDBServerInfo, + TEST_ATLAS_CLOUD_EXTERNAL_URL, } from './helpers/compass'; import ResultLogger from './helpers/result-logger'; @@ -60,52 +61,54 @@ async function setup() { const disableStartStop = process.argv.includes('--disable-start-stop'); const shouldTestCompassWeb = process.argv.includes('--test-compass-web'); - // When working on the tests it is faster to just keep the server running. - if (!disableStartStop) { - debug('Starting MongoDB server'); - crossSpawn.sync('npm', ['run', 'start-servers'], { stdio: 'inherit' }); - - if (shouldTestCompassWeb) { - debug('Starting Compass Web'); - compassWeb = crossSpawn.spawn( - 'npm', - ['run', '--unsafe-perm', 'start-web'], - { - cwd: path.resolve(__dirname, '..', '..'), - env: { - ...process.env, - OPEN_BROWSER: 'false', // tell webpack dev server not to open the default browser - DISABLE_DEVSERVER_OVERLAY: 'true', - APP_ENV: 'webdriverio', - }, + if (!TEST_ATLAS_CLOUD_EXTERNAL_URL) { + // When working on the tests it is faster to just keep the server running. + if (!disableStartStop) { + debug('Starting MongoDB server'); + crossSpawn.sync('npm', ['run', 'start-servers'], { stdio: 'inherit' }); + + if (shouldTestCompassWeb) { + debug('Starting Compass Web'); + compassWeb = crossSpawn.spawn( + 'npm', + ['run', '--unsafe-perm', 'start-web'], + { + cwd: path.resolve(__dirname, '..', '..'), + env: { + ...process.env, + OPEN_BROWSER: 'false', // tell webpack dev server not to open the default browser + DISABLE_DEVSERVER_OVERLAY: 'true', + APP_ENV: 'webdriverio', + }, + } + ); + + compassWeb.stdout.pipe(process.stdout); + compassWeb.stderr.pipe(process.stderr); + + let serverReady = false; + const start = Date.now(); + while (!serverReady) { + if (Date.now() - start >= 120_000) { + throw new Error( + 'The compass-web sandbox is still not running after 120000ms' + ); + } + try { + const res = await fetch('http://localhost:7777'); + serverReady = res.ok; + debug('Web server ready: %s', serverReady); + } catch (err) { + debug('Failed to connect to dev server: %s', (err as any).message); + } + await wait(1000); } - ); - - compassWeb.stdout.pipe(process.stdout); - compassWeb.stderr.pipe(process.stderr); - - let serverReady = false; - const start = Date.now(); - while (!serverReady) { - if (Date.now() - start >= 120_000) { - throw new Error( - 'The compass-web sandbox is still not running after 120000ms' - ); - } - try { - const res = await fetch('http://localhost:7777'); - serverReady = res.ok; - debug('Web server ready: %s', serverReady); - } catch (err) { - debug('Failed to connect to dev server: %s', (err as any).message); - } - await wait(1000); + } else { + debug('Writing electron-versions.json'); + crossSpawn.sync('scripts/write-electron-versions.sh', [], { + stdio: 'inherit', + }); } - } else { - debug('Writing electron-versions.json'); - crossSpawn.sync('scripts/write-electron-versions.sh', [], { - stdio: 'inherit', - }); } } @@ -139,34 +142,36 @@ function cleanup() { const disableStartStop = process.argv.includes('--disable-start-stop'); const shouldTestCompassWeb = process.argv.includes('--test-compass-web'); - if (!disableStartStop) { - if (shouldTestCompassWeb) { - debug('Stopping compass-web'); - try { - if (compassWeb.pid) { - debug(`killing compass-web [${compassWeb.pid}]`); - kill(compassWeb.pid, 'SIGINT'); - } else { - debug('no pid for compass-web'); + if (!TEST_ATLAS_CLOUD_EXTERNAL_URL) { + if (!disableStartStop) { + if (shouldTestCompassWeb) { + debug('Stopping compass-web'); + try { + if (compassWeb.pid) { + debug(`killing compass-web [${compassWeb.pid}]`); + kill(compassWeb.pid, 'SIGINT'); + } else { + debug('no pid for compass-web'); + } + } catch (e) { + debug('Failed to stop compass-web', e); } - } catch (e) { - debug('Failed to stop compass-web', e); } - } - debug('Stopping MongoDB server'); - try { - crossSpawn.sync('npm', ['run', 'stop-servers'], { - // If it's taking too long we might as well kill the process and move on, - // mongodb-runner is flaky sometimes and in ci `posttest-ci` script will - // take care of additional clean up anyway - timeout: 120_000, - stdio: 'inherit', - }); - } catch (e) { - debug('Failed to stop MongoDB Server', e); + debug('Stopping MongoDB server'); + try { + crossSpawn.sync('npm', ['run', 'stop-servers'], { + // If it's taking too long we might as well kill the process and move on, + // mongodb-runner is flaky sometimes and in ci `posttest-ci` script will + // take care of additional clean up anyway + timeout: 120_000, + stdio: 'inherit', + }); + } catch (e) { + debug('Failed to stop MongoDB Server', e); + } + debug('Done stopping'); } - debug('Done stopping'); } // Since the webdriverio update something is messing with the terminal's @@ -258,9 +263,10 @@ async function main() { const e2eTestGroupsAmount = parseInt(process.env.E2E_TEST_GROUPS || '1'); const e2eTestGroup = parseInt(process.env.E2E_TEST_GROUP || '1'); + const e2eTestFilter = process.env.E2E_TEST_FILTER || '*'; - const rawTests = ( - await glob('tests/**/*.{test,spec}.ts', { + const tests = ( + await glob(`tests/**/${e2eTestFilter}.{test,spec}.ts`, { cwd: __dirname, }) ).filter((value, index, array) => { @@ -271,7 +277,7 @@ async function main() { return index >= minGroupIndex && index <= maxGroupIndex; }); - console.info('Test files:', rawTests); + console.info('Test files:', tests); // The only test file that's interested in the first-run experience (at the // time of writing) is time-to-first-query.ts and that happens to be @@ -279,7 +285,11 @@ async function main() { // will also get the slow first run experience for no good reason unless it is // the time-to-first-query.ts test. // So yeah.. this is a bit of a micro optimisation. - const tests = [FIRST_TEST, ...rawTests.filter((t) => t !== FIRST_TEST)]; + tests.sort((a, b) => { + if (a === FIRST_TEST) return -1; + else if (b === FIRST_TEST) return 1; + else return 0; + }); // Ensure the insert-data mocha hooks are run. tests.unshift(path.join('helpers', 'insert-data.ts')); From b1bc75e5721196ea15b3b207ed0a87ef4ff42396 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:10:36 +0000 Subject: [PATCH 2/6] chore: update AUTHORS, THIRD-PARTY-NOTICES, Security Test Summary (#6388) Update report Co-authored-by: syn-zhu <167124917+syn-zhu@users.noreply.github.com> --- AUTHORS | 1 + docs/tracking-plan.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index e9a04ad3d3d..9afdce0409c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -92,3 +92,4 @@ admin-token-bot <36773031+admin-token-bot@users.noreply.github.com> Kræn Hansen Kræn Hansen Ruchitha Rajaghatta <77162985+ruchitharajaghatta@users.noreply.github.com> +syn-zhu <167124917+syn-zhu@users.noreply.github.com> diff --git a/docs/tracking-plan.md b/docs/tracking-plan.md index e3c5260cc42..cbe15b7a7b3 100644 --- a/docs/tracking-plan.md +++ b/docs/tracking-plan.md @@ -1,7 +1,7 @@ # Compass Tracking Plan -Generated on Tue, Oct 22, 2024 at 02:00 PM +Generated on Tue, Oct 22, 2024 at 04:10 PM ## Table of Contents From 911b18033556c75c6fbce4c98f3b834a92590616 Mon Sep 17 00:00:00 2001 From: Sergey Petushkov Date: Wed, 23 Oct 2024 13:35:50 +0200 Subject: [PATCH 3/6] chore(deps): update node-gyp to 10; remove workaround for windows; remove python 3.6 from path COMPASS-8346 (#6390) chore(deps): update node-gyp to 10; remove workaround for windows; remove python 3.6 from path --- .evergreen/node-gyp-bug-workaround.sh | 53 -- .evergreen/preinstall.sh | 3 - .evergreen/print-compass-env.js | 17 +- package-lock.json | 929 +++++++++++++++++++------- package.json | 2 +- 5 files changed, 708 insertions(+), 296 deletions(-) delete mode 100755 .evergreen/node-gyp-bug-workaround.sh diff --git a/.evergreen/node-gyp-bug-workaround.sh b/.evergreen/node-gyp-bug-workaround.sh deleted file mode 100755 index 36b233a0fbb..00000000000 --- a/.evergreen/node-gyp-bug-workaround.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env bash - -# This is a workaround for a node-gyp bug that has not fully been investigated -# due to problems reproducing it outside of CI environments (even though it -# occurs both in evergreen and github actions). -# Something seems to go wrong when node-gyp extracts the Node.js header tarball, -# on Windows specifically (this is most likely because node-tar treats -# the overwriting of existing files differently on Windows than on other OS -- -# for good reasons, but still). -# The most likely cause of this issue is that node-gyp somehow extracts the -# same headers tarball twice, in parallel, in the same location, with race -# conditions in the tar extraction code leading to issues. -# The extraction result ends up in %LOCALAPPDATA%\node-gyp\Cache. -# Manually extracting the tarballs will solve this issue, so we're doing that -# here. -# For actually resolving the bug, we would probably need somebody with a local -# reproduction. However, it seems likely that other people will also encounter -# this issue, so there's also a good chance that this workaround will just -# not be needed with a future node-gyp version. - -if [ x"$NODE_JS_VERSION" = x"" ]; then - if node -v; then - export NODE_JS_VERSION=$(node -p 'process.version.slice(1)') - else - echo "Need NODE_JS_VERSION to be set or Node.js to be installed for node-gyp bug workaround script" - exit 1 - fi -fi - -if [ x"$LOCALAPPDATA" = x"" ]; then - echo "No LOCALAPPDATA set, ignoring node-gyp bug workaround script" - exit -fi - -set -ex - -SCRIPTDIR="$(cd $(dirname "$0"); pwd)" -CACHEDIR="$LOCALAPPDATA/node-gyp/Cache" -rm -rvf "$CACHEDIR" -mkdir -p "$CACHEDIR/$NODE_JS_VERSION" -cd "$CACHEDIR/$NODE_JS_VERSION" - -bash "${SCRIPTDIR}/retry-with-backoff.sh" curl -sSfLO "https://nodejs.org/download/release/v$NODE_JS_VERSION/node-v$NODE_JS_VERSION-headers.tar.gz" -tar --strip-components=1 -xvzf "node-v$NODE_JS_VERSION-headers.tar.gz" -for arch in x64 x86 arm64; do - mkdir $arch - pushd $arch - bash "${SCRIPTDIR}/retry-with-backoff.sh" curl -sSfLO "https://nodejs.org/download/release/v$NODE_JS_VERSION/win-$arch/node.lib" || echo "no $arch v$NODE_JS_VERSION .lib file" - popd -done - -# Finally, store the right installVersion value for current node-gyp versions -echo 9 > installVersion diff --git a/.evergreen/preinstall.sh b/.evergreen/preinstall.sh index a0e247396e0..bac0ce1249e 100755 --- a/.evergreen/preinstall.sh +++ b/.evergreen/preinstall.sh @@ -39,9 +39,6 @@ if [ -n "$IS_WINDOWS" ]; then ./node.exe node_modules/npm2/bin/npm-cli.js i -g npm@$NPM_VERSION rm -rf node_modules/npm2/ chmod +x npm.cmd npm - - cd .. - .evergreen/node-gyp-bug-workaround.sh else if command -v ldd &> /dev/null && `ldd $(which bash) | grep 'libc.so' | awk '{print $3}'` | grep -Eq 'release version 2.(1|2[0-7])'; then echo "Installing unofficial nodejs compiled for glibc 2.17 v${NODE_JS_VERSION} for ${PLATFORM} on ${ARCH}..." diff --git a/.evergreen/print-compass-env.js b/.evergreen/print-compass-env.js index 5f76ce0bc65..b9efa68f58f 100755 --- a/.evergreen/print-compass-env.js +++ b/.evergreen/print-compass-env.js @@ -68,13 +68,10 @@ function printCompassEnv() { } if (process.env.PLATFORM === 'linux') { - // To build node modules on linux post electron 13 we need - // a newer c++ compiler version, this adds it. + // To build node modules on linux post electron 13 we need a newer c++ + // compiler version and at least python v3.9, this adds it. // https://jira.mongodb.org/browse/COMPASS-5150 pathsToPrepend.unshift('/opt/mongodbtoolchain/v3/bin'); - - // Make sure that linux is using python 3.6 (node-gyp requirement) - pathsToPrepend.unshift('/opt/python/3.6/bin'); } PATH = maybePrependPaths(PATH, pathsToPrepend); @@ -103,10 +100,16 @@ function printCompassEnv() { printVar('IS_RHEL', process.env.IS_RHEL); printVar('IS_UBUNTU', process.env.IS_UBUNTU); printVar('DEBUG', process.env.DEBUG); - printVar('MONGODB_VERSION', process.env.MONGODB_VERSION || process.env.MONGODB_DEFAULT_VERSION); + printVar( + 'MONGODB_VERSION', + process.env.MONGODB_VERSION || process.env.MONGODB_DEFAULT_VERSION + ); printVar('DEV_VERSION_IDENTIFIER', process.env.DEV_VERSION_IDENTIFIER); printVar('EVERGREEN_REVISION', process.env.EVERGREEN_REVISION); - printVar('EVERGREEN_REVISION_ORDER_ID', process.env.EVERGREEN_REVISION_ORDER_ID); + printVar( + 'EVERGREEN_REVISION_ORDER_ID', + process.env.EVERGREEN_REVISION_ORDER_ID + ); if (process.platform === 'darwin') { // Without this, kerberos 2.1.1 is broken on macOS, but this flag is only diff --git a/package-lock.json b/package-lock.json index 71e5a8b4954..6b76ddb88c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "js-yaml": "^4.1.0", "lerna": "^7.1.5", "lodash": "^4.17.21", - "node-gyp": "^8.4.1" + "node-gyp": "^10.2.0" }, "engines": { "node": ">=20.15.0", @@ -9052,6 +9052,30 @@ "node": ">= 8" } }, + "node_modules/@npmcli/agent": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", + "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", + "dev": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/@npmcli/arborist": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-6.2.0.tgz", @@ -30602,6 +30626,270 @@ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" }, + "node_modules/make-fetch-happen": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", + "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^2.0.0", + "cacache": "^18.0.0", + "http-cache-semantics": "^4.1.1", + "is-lambda": "^1.0.1", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/@npmcli/fs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", + "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "dev": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/cacache": { + "version": "18.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", + "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/make-fetch-happen/node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/make-fetch-happen/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/make-fetch-happen/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/make-fetch-happen/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/make-fetch-happen/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/make-fetch-happen/node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/make-fetch-happen/node_modules/minipass-fetch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", + "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/make-fetch-happen/node_modules/proc-log": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/make-fetch-happen/node_modules/ssri": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", + "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/unique-filename": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", + "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "dev": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/unique-slug": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", + "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/map-obj": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.2.1.tgz", @@ -32408,27 +32696,28 @@ } }, "node_modules/node-gyp": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", - "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.2.0.tgz", + "integrity": "sha512-sp3FonBAaFe4aYTcFdZUn2NYkbP7xroPGYvQmP4Nl5PxamznItBnNCgjrVTKrEfQynInMsJvZrdmqUnysCJ8rw==", "dev": true, + "license": "MIT", "dependencies": { "env-paths": "^2.2.0", - "glob": "^7.1.4", + "exponential-backoff": "^3.1.1", + "glob": "^10.3.10", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^9.1.0", - "nopt": "^5.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", + "make-fetch-happen": "^13.0.0", + "nopt": "^7.0.0", + "proc-log": "^4.1.0", "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" + "tar": "^6.2.1", + "which": "^4.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": ">= 10.12.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/node-gyp-build": { @@ -32442,171 +32731,169 @@ "node-gyp-build-test": "build-test.js" } }, - "node_modules/node-gyp/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "node_modules/node-gyp/node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", "dev": true, - "license": "MIT", - "dependencies": { - "debug": "4" - }, + "license": "ISC", "engines": { - "node": ">= 6.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/node-gyp/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/node-gyp/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, - "engines": { - "node": ">=8" + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" } }, - "node_modules/node-gyp/node_modules/are-we-there-yet": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.0.tgz", - "integrity": "sha512-0GWpv50YSOcLXaN6/FAKY3vfRbllXWV2xvfA/oKJF8pzFhWXPV+yjhJXDBbjscDYowv7Yw1A3uigpzn5iEGTyw==", + "node_modules/node-gyp/node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", "dev": true, + "license": "ISC", "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/node-gyp/node_modules/gauge": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.2.tgz", - "integrity": "sha512-aSPRm2CvA9R8QyU5eXMFPd+cYkyxLsXHd2l5/FOH2V/eml//M04G6KZOmTap07O1PvEwNcl2NndyLfK8g3QrKA==", + "node_modules/node-gyp/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, + "license": "ISC", "dependencies": { - "ansi-regex": "^5.0.1", - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16" + "node": ">=16" } }, - "node_modules/node-gyp/node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "node_modules/node-gyp/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" + "@isaacs/cliui": "^8.0.2" }, - "engines": { - "node": ">= 6" + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/node-gyp/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "node_modules/node-gyp/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "agent-base": "6", - "debug": "4" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">= 6" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/node-gyp/node_modules/make-fetch-happen": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", - "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "node_modules/node-gyp/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, - "dependencies": { - "agentkeepalive": "^4.1.3", - "cacache": "^15.2.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^6.0.0", - "minipass": "^3.1.3", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^1.3.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.2", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^6.0.0", - "ssri": "^8.0.0" - }, + "license": "ISC", "engines": { - "node": ">= 10" + "node": ">=16 || 14 >=14.17" } }, "node_modules/node-gyp/node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", "dev": true, + "license": "ISC", "dependencies": { - "abbrev": "1" + "abbrev": "^2.0.0" }, "bin": { "nopt": "bin/nopt.js" }, "engines": { - "node": ">=6" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/node-gyp/node_modules/npmlog": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.1.tgz", - "integrity": "sha512-BTHDvY6nrRHuRfyjt1MAufLxYdVXZfd099H4+i1f0lPywNQyI4foeNXJRObB/uy+TYqUW0vAD9gbdSOXPst7Eg==", + "node_modules/node-gyp/node_modules/proc-log": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", "dev": true, - "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.0", - "set-blocking": "^2.0.0" - }, + "license": "ISC", "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/node-gyp/node_modules/socks-proxy-agent": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz", - "integrity": "sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==", + "node_modules/node-gyp/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.1", - "socks": "^2.6.1" - }, + "license": "ISC", "engines": { - "node": ">= 10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/node-gyp/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/node-gyp/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, + "license": "ISC", "dependencies": { - "ansi-regex": "^5.0.1" + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" }, "engines": { - "node": ">=8" + "node": "^16.13.0 || >=18.0.0" } }, "node_modules/node-machine-id": { @@ -40101,9 +40388,10 @@ } }, "node_modules/tar": { - "version": "6.1.15", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz", - "integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "license": "ISC", "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -60472,6 +60760,27 @@ "fastq": "^1.6.0" } }, + "@npmcli/agent": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", + "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", + "dev": true, + "requires": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "dependencies": { + "lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + } + } + }, "@npmcli/arborist": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-6.2.0.tgz", @@ -78601,6 +78910,190 @@ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" }, + "make-fetch-happen": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", + "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", + "dev": true, + "requires": { + "@npmcli/agent": "^2.0.0", + "cacache": "^18.0.0", + "http-cache-semantics": "^4.1.1", + "is-lambda": "^1.0.1", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1", + "ssri": "^10.0.0" + }, + "dependencies": { + "@npmcli/fs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", + "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "dev": true, + "requires": { + "semver": "^7.3.5" + } + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "cacache": { + "version": "18.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", + "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", + "dev": true, + "requires": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + } + }, + "foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + } + }, + "fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "requires": { + "minipass": "^7.0.3" + } + }, + "glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "requires": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + } + }, + "jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "requires": { + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" + } + }, + "lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true + }, + "minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, + "requires": { + "minipass": "^7.0.3" + } + }, + "minipass-fetch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", + "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", + "dev": true, + "requires": { + "encoding": "^0.1.13", + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + } + }, + "proc-log": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "dev": true + }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + }, + "ssri": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", + "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "dev": true, + "requires": { + "minipass": "^7.0.3" + } + }, + "unique-filename": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", + "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "dev": true, + "requires": { + "unique-slug": "^4.0.0" + } + }, + "unique-slug": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", + "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + } + } + }, "map-obj": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.2.1.tgz", @@ -80487,149 +80980,121 @@ "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" }, "node-gyp": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", - "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.2.0.tgz", + "integrity": "sha512-sp3FonBAaFe4aYTcFdZUn2NYkbP7xroPGYvQmP4Nl5PxamznItBnNCgjrVTKrEfQynInMsJvZrdmqUnysCJ8rw==", "dev": true, "requires": { "env-paths": "^2.2.0", - "glob": "^7.1.4", + "exponential-backoff": "^3.1.1", + "glob": "^10.3.10", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^9.1.0", - "nopt": "^5.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", + "make-fetch-happen": "^13.0.0", + "nopt": "^7.0.0", + "proc-log": "^4.1.0", "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" + "tar": "^6.2.1", + "which": "^4.0.0" }, "dependencies": { - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", "dev": true }, - "are-we-there-yet": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.0.tgz", - "integrity": "sha512-0GWpv50YSOcLXaN6/FAKY3vfRbllXWV2xvfA/oKJF8pzFhWXPV+yjhJXDBbjscDYowv7Yw1A3uigpzn5iEGTyw==", + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "requires": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" + "balanced-match": "^1.0.0" } }, - "gauge": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.2.tgz", - "integrity": "sha512-aSPRm2CvA9R8QyU5eXMFPd+cYkyxLsXHd2l5/FOH2V/eml//M04G6KZOmTap07O1PvEwNcl2NndyLfK8g3QrKA==", + "foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", "dev": true, "requires": { - "ansi-regex": "^5.0.1", - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" } }, - "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" } }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true + }, + "jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "requires": { - "agent-base": "6", - "debug": "4" + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" } }, - "make-fetch-happen": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", - "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "requires": { - "agentkeepalive": "^4.1.3", - "cacache": "^15.2.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^6.0.0", - "minipass": "^3.1.3", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^1.3.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.2", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^6.0.0", - "ssri": "^8.0.0" + "brace-expansion": "^2.0.1" } }, + "minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true + }, "nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", "dev": true, "requires": { - "abbrev": "1" + "abbrev": "^2.0.0" } }, - "npmlog": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.1.tgz", - "integrity": "sha512-BTHDvY6nrRHuRfyjt1MAufLxYdVXZfd099H4+i1f0lPywNQyI4foeNXJRObB/uy+TYqUW0vAD9gbdSOXPst7Eg==", - "dev": true, - "requires": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.0", - "set-blocking": "^2.0.0" - } + "proc-log": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "dev": true }, - "socks-proxy-agent": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz", - "integrity": "sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==", - "dev": true, - "requires": { - "agent-base": "^6.0.2", - "debug": "^4.3.1", - "socks": "^2.6.1" - } + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, "requires": { - "ansi-regex": "^5.0.1" + "isexe": "^3.1.1" } } } @@ -86394,9 +86859,9 @@ "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==" }, "tar": { - "version": "6.1.15", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz", - "integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "requires": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", diff --git a/package.json b/package.json index 0dec690b267..f2bb606cfd4 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "js-yaml": "^4.1.0", "lerna": "^7.1.5", "lodash": "^4.17.21", - "node-gyp": "^8.4.1" + "node-gyp": "^10.2.0" }, "engines": { "node": ">=20.15.0", From c72a1df872eb53a77d37ade2488102c718f2e6d9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 23 Oct 2024 11:42:47 +0000 Subject: [PATCH 4/6] chore: update AUTHORS, THIRD-PARTY-NOTICES, Security Test Summary (#6391) Update report Co-authored-by: gribnoysup <5036933+gribnoysup@users.noreply.github.com> --- THIRD-PARTY-NOTICES.md | 2 +- docs/tracking-plan.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/THIRD-PARTY-NOTICES.md b/THIRD-PARTY-NOTICES.md index cc6829fa259..22c71e496e5 100644 --- a/THIRD-PARTY-NOTICES.md +++ b/THIRD-PARTY-NOTICES.md @@ -1,5 +1,5 @@ The following third-party software is used by and included in **Mongodb Compass**. -This document was automatically generated on Tue Oct 22 2024. +This document was automatically generated on Wed Oct 23 2024. ## List of dependencies diff --git a/docs/tracking-plan.md b/docs/tracking-plan.md index cbe15b7a7b3..4f6b27fd4a0 100644 --- a/docs/tracking-plan.md +++ b/docs/tracking-plan.md @@ -1,7 +1,7 @@ # Compass Tracking Plan -Generated on Tue, Oct 22, 2024 at 04:10 PM +Generated on Wed, Oct 23, 2024 at 11:42 AM ## Table of Contents From ce0d66f2196a2be15cf1255a08b33190fbd008e3 Mon Sep 17 00:00:00 2001 From: Sergey Petushkov Date: Wed, 23 Oct 2024 13:52:44 +0200 Subject: [PATCH 5/6] chore(e2e): restructure e2e test helpers to better separate shared state and setup / teardown functions COMPASS-8361 (#6383) * chore(e2e): restructure e2e test helpers to better separate shared state and setup / teardown functions * chore: fix depcheck and var name * chore: remove TEST_PACKAGED_APP env var usage * chore: replset for test servers and better cleanup * chore: eslint fixes * chore(e2e): use DEFAULT_CONNECTIONS in connect form helpers * chore: ts fix * chore: update to non-esm version * chore: fix glob * chore: remove unused imports * chore: more glob adjustments * chore: uncomment retries * chore: fix env var name --- .evergreen/functions.yml | 2 +- package-lock.json | 49 +- package.json | 2 - packages/compass-e2e-tests/.depcheckrc | 7 +- packages/compass-e2e-tests/.gitignore | 2 - packages/compass-e2e-tests/.prettierignore | 4 - .../helpers/commands/connect-form.ts | 78 +--- .../helpers/commands/screenshot.ts | 4 +- packages/compass-e2e-tests/helpers/compass.ts | 218 ++++----- packages/compass-e2e-tests/helpers/gunzip.ts | 41 ++ .../compass-e2e-tests/helpers/insert-data.ts | 18 +- .../helpers/result-logger.ts | 340 -------------- .../helpers/test-runner-context.ts | 193 ++++++++ .../helpers/test-runner-global-fixtures.ts | 225 +++++++++ packages/compass-e2e-tests/index.ts | 427 +++++------------- packages/compass-e2e-tests/package.json | 9 +- packages/compass-e2e-tests/scripts/gunzip.ts | 37 -- .../compass-e2e-tests/scripts/server-info.ts | 27 -- .../scripts/write-electron-versions.sh | 18 - packages/compass-web/package.json | 1 + packages/compass/package.json | 2 +- 21 files changed, 737 insertions(+), 967 deletions(-) create mode 100755 packages/compass-e2e-tests/helpers/gunzip.ts delete mode 100644 packages/compass-e2e-tests/helpers/result-logger.ts create mode 100644 packages/compass-e2e-tests/helpers/test-runner-context.ts create mode 100644 packages/compass-e2e-tests/helpers/test-runner-global-fixtures.ts delete mode 100755 packages/compass-e2e-tests/scripts/gunzip.ts delete mode 100644 packages/compass-e2e-tests/scripts/server-info.ts delete mode 100755 packages/compass-e2e-tests/scripts/write-electron-versions.sh diff --git a/.evergreen/functions.yml b/.evergreen/functions.yml index acf5b475190..4124ba293d1 100644 --- a/.evergreen/functions.yml +++ b/.evergreen/functions.yml @@ -664,7 +664,7 @@ functions: DEBUG: ${debug|} MONGODB_VERSION: ${mongodb_version|} MONGODB_RUNNER_VERSION: ${mongodb_version|} - BROWSER_NAME: ${browser_name} + COMPASS_WEB_BROWSER_NAME: ${browser_name} E2E_TEST_GROUPS: ${e2e_test_groups} E2E_TEST_GROUP: ${e2e_test_group} script: | diff --git a/package-lock.json b/package-lock.json index 6b76ddb88c5..8b2d922b7c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31757,6 +31757,7 @@ "version": "1.7.2", "resolved": "https://registry.npmjs.org/mongodb-build-info/-/mongodb-build-info-1.7.2.tgz", "integrity": "sha512-eoLFZvCIjcwijYJdxvYupj1c+55VAVm0o4gBJjrcDxxmmpm+bC4Ix9ayZbyhQdVXDZAGDi03NA0GghXjBVXnxg==", + "license": "Apache-2.0", "dependencies": { "mongodb-connection-string-url": "^3.0.0" } @@ -44485,13 +44486,14 @@ "debug": "^4.3.4", "depcheck": "^1.4.1", "electron": "^30.5.1", + "electron-to-chromium": "^1.5.41", "eslint": "^7.25.0", - "fast-glob": "^3.2.7", "glob": "^10.2.5", "hadron-build": "^25.5.12", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb": "^6.9.0", + "mongodb-build-info": "^1.7.2", "mongodb-connection-string-url": "^3.0.1", "mongodb-log-writer": "^1.4.2", "mongodb-runner": "^5.6.3", @@ -44505,6 +44507,7 @@ "tree-kill": "^1.2.2", "ts-node": "^10.9.1", "webdriverio": "^8.40.0", + "why-is-node-running": "^2.3.0", "xvfb-maybe": "^0.2.1" } }, @@ -44531,6 +44534,13 @@ "node": ">=8" } }, + "packages/compass-e2e-tests/node_modules/electron-to-chromium": { + "version": "1.5.41", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.41.tgz", + "integrity": "sha512-dfdv/2xNjX0P8Vzme4cfzHqnPm5xsZXwsolTYr0eyW18IUmNyG08vL+fttvinTfhKfIKdRoqkDIC9e9iWQCNYQ==", + "dev": true, + "license": "ISC" + }, "packages/compass-e2e-tests/node_modules/execa": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", @@ -44779,6 +44789,23 @@ "webidl-conversions": "^3.0.0" } }, + "packages/compass-e2e-tests/node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "packages/compass-editor": { "name": "@mongodb-js/compass-editor", "version": "0.31.1", @@ -68055,13 +68082,14 @@ "debug": "^4.3.4", "depcheck": "^1.4.1", "electron": "^30.5.1", + "electron-to-chromium": "^1.5.41", "eslint": "^7.25.0", - "fast-glob": "^3.2.7", "glob": "^10.2.5", "hadron-build": "^25.5.12", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb": "^6.9.0", + "mongodb-build-info": "^1.7.2", "mongodb-connection-string-url": "^3.0.1", "mongodb-log-writer": "^1.4.2", "mongodb-runner": "^5.6.3", @@ -68075,6 +68103,7 @@ "tree-kill": "^1.2.2", "ts-node": "^10.9.1", "webdriverio": "^8.40.0", + "why-is-node-running": "^2.3.0", "xvfb-maybe": "^0.2.1" }, "dependencies": { @@ -68098,6 +68127,12 @@ "is-wsl": "^2.1.1" } }, + "electron-to-chromium": { + "version": "1.5.41", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.41.tgz", + "integrity": "sha512-dfdv/2xNjX0P8Vzme4cfzHqnPm5xsZXwsolTYr0eyW18IUmNyG08vL+fttvinTfhKfIKdRoqkDIC9e9iWQCNYQ==", + "dev": true + }, "execa": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", @@ -68272,6 +68307,16 @@ "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } + }, + "why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "requires": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + } } } }, diff --git a/package.json b/package.json index f2bb606cfd4..30538aae301 100644 --- a/package.json +++ b/package.json @@ -32,8 +32,6 @@ "package-compass": "npm run package-compass --workspace=mongodb-compass --", "package-compass-debug": "npm run package-compass-debug --workspace=mongodb-compass --", "package-compass-nocompile": "npm run package-compass-nocompile --workspace=mongodb-compass --", - "prestart": "npm run compile --workspace=@mongodb-js/webpack-config-compass", - "prestart-web": "npm run prestart", "start": "npm run start --workspace=mongodb-compass", "start-web": "npm run start --workspace=@mongodb-js/compass-web", "test": "lerna run test --concurrency 1 --stream", diff --git a/packages/compass-e2e-tests/.depcheckrc b/packages/compass-e2e-tests/.depcheckrc index a14dc086cad..e3dc5af3444 100644 --- a/packages/compass-e2e-tests/.depcheckrc +++ b/packages/compass-e2e-tests/.depcheckrc @@ -2,9 +2,12 @@ ignores: - '@mongodb-js/prettier-config-compass' - '@mongodb-js/tsconfig-compass' - '@wdio/types' - - 'mongodb-compass' - 'ps-list' - - 'mongodb-runner' +# Avoiding recursive deps on monorepo workspaces + - 'mongodb-compass' + - '@mongodb-js/mocha-config-compass' + - 'compass-e2e-tests' + - '@mongodb-js/compass-web' # TODO(COMPASS-8312): depcheck doesn't count this dependency because it's a # types only import and the depcheck parser is resolving the @types/ # package as a dep instead diff --git a/packages/compass-e2e-tests/.gitignore b/packages/compass-e2e-tests/.gitignore index f02217af9e4..74d7b19ca26 100644 --- a/packages/compass-e2e-tests/.gitignore +++ b/packages/compass-e2e-tests/.gitignore @@ -2,5 +2,3 @@ .log fixtures/*.csv fixtures/*.json -write-electron-versions.js -electron-versions.json diff --git a/packages/compass-e2e-tests/.prettierignore b/packages/compass-e2e-tests/.prettierignore index 3f0e8eed2a2..104b57fc109 100644 --- a/packages/compass-e2e-tests/.prettierignore +++ b/packages/compass-e2e-tests/.prettierignore @@ -3,7 +3,3 @@ fixtures .nyc_output coverage - -# Generated files -electron-versions.json -write-electron-versions.js diff --git a/packages/compass-e2e-tests/helpers/commands/connect-form.ts b/packages/compass-e2e-tests/helpers/commands/connect-form.ts index d6a11d99bbd..90af05ad2b2 100644 --- a/packages/compass-e2e-tests/helpers/commands/connect-form.ts +++ b/packages/compass-e2e-tests/helpers/commands/connect-form.ts @@ -3,15 +3,10 @@ import { expect } from 'chai'; import type { CompassBrowser } from '../compass-browser'; import * as Selectors from '../selectors'; import type { ConnectFormState } from '../connect-form-state'; -import { - DEFAULT_CONNECTION_NAME_1, - DEFAULT_CONNECTION_NAME_2, - DEFAULT_CONNECTION_STRING_1, - DEFAULT_CONNECTION_STRING_2, - TEST_ATLAS_CLOUD_EXTERNAL_URL, - TEST_MULTIPLE_CONNECTIONS, -} from '../compass'; +import { TEST_MULTIPLE_CONNECTIONS } from '../compass'; import Debug from 'debug'; +import { DEFAULT_CONNECTIONS } from '../test-runner-context'; +import { getConnectionTitle } from '@mongodb-js/connection-info'; const debug = Debug('compass-e2e-tests'); export async function resetConnectForm(browser: CompassBrowser): Promise { @@ -939,29 +934,13 @@ export async function setConnectFormState( export async function saveConnection( browser: CompassBrowser, - state: ConnectFormState, - - // TODO(COMPASS-8023): Just remove these once the single connection code is removed - favouriteName: string, - color: string + state: ConnectFormState ): Promise { await browser.setConnectFormState(state); - if (TEST_MULTIPLE_CONNECTIONS) { - await browser.clickVisible(Selectors.ConnectionModalSaveButton); - await browser - .$(Selectors.ConnectionModal) - .waitForDisplayed({ reverse: true }); - } else { - await browser.clickVisible(Selectors.ConnectionEditFavouriteButton); - await browser.$(Selectors.FavoriteModal).waitForDisplayed(); - await browser.setValueVisible(Selectors.FavoriteNameInput, favouriteName); - await browser.clickVisible( - `${Selectors.FavoriteColorSelector} [data-testid="color-pick-${color}"]` - ); - await browser.$(Selectors.FavoriteSaveButton).waitForEnabled(); - await browser.clickVisible(Selectors.FavoriteSaveButton); - await browser.$(Selectors.FavoriteModal).waitForExist({ reverse: true }); - } + await browser.clickVisible(Selectors.ConnectionModalSaveButton); + await browser + .$(Selectors.ConnectionModal) + .waitForDisplayed({ reverse: true }); } export async function setupDefaultConnections(browser: CompassBrowser) { @@ -987,16 +966,8 @@ export async function setupDefaultConnections(browser: CompassBrowser) { whereas we do have some tests that try and use those. We can easily change this in future if needed, though. */ - - // no need to setup connections if we are running against Atlas - if (TEST_ATLAS_CLOUD_EXTERNAL_URL) { - return; - } - - for (const connectionName of [ - DEFAULT_CONNECTION_NAME_1, - DEFAULT_CONNECTION_NAME_2, - ]) { + for (const connectionInfo of DEFAULT_CONNECTIONS) { + const connectionName = getConnectionTitle(connectionInfo); if (await browser.removeConnection(connectionName)) { debug('Removing existing connection so we do not create a duplicate', { connectionName, @@ -1004,28 +975,11 @@ export async function setupDefaultConnections(browser: CompassBrowser) { } } - await browser.saveConnection( - { - connectionString: DEFAULT_CONNECTION_STRING_1, - // NOTE: no connectionName, we're going with the auto-generated one. Also no - // connectionColor. Passing a name and colour for single connection world, - // though, because that's the only way to create a favourite. - }, - DEFAULT_CONNECTION_NAME_1, - 'color1' - ); - - // no need for a second connection in single connection mode - if (TEST_MULTIPLE_CONNECTIONS) { - await browser.saveConnection( - { - connectionString: DEFAULT_CONNECTION_STRING_2, - // NOTE: filling in a name so that this one does _not_ have the auto-generated one - connectionName: DEFAULT_CONNECTION_NAME_2, - connectionColor: 'Iris', - }, - DEFAULT_CONNECTION_NAME_2, - 'color8' - ); + for (const connectionInfo of DEFAULT_CONNECTIONS) { + await browser.saveConnection({ + connectionString: connectionInfo.connectionOptions.connectionString, + connectionName: connectionInfo.favorite?.name, + connectionColor: connectionInfo.favorite?.color, + }); } } diff --git a/packages/compass-e2e-tests/helpers/commands/screenshot.ts b/packages/compass-e2e-tests/helpers/commands/screenshot.ts index 7a7a89f0c53..5b01dc7ae4f 100644 --- a/packages/compass-e2e-tests/helpers/commands/screenshot.ts +++ b/packages/compass-e2e-tests/helpers/commands/screenshot.ts @@ -1,6 +1,6 @@ import path from 'path'; import type { CompassBrowser } from '../compass-browser'; -import { SCREENSHOTS_PATH } from '../compass'; +import { LOG_SCREENSHOTS_PATH } from '../test-runner-context'; const withTimeout = (millis: number, promise: Promise) => { let timeoutPid: NodeJS.Timeout; @@ -26,7 +26,7 @@ export async function screenshot( // are still in progress or not. await browser.pause(1000); - const fullPath = path.join(SCREENSHOTS_PATH, filename); + const fullPath = path.join(LOG_SCREENSHOTS_PATH, filename); try { await withTimeout(10000, browser.saveScreenshot(fullPath)); } catch (err: any) { diff --git a/packages/compass-e2e-tests/helpers/compass.ts b/packages/compass-e2e-tests/helpers/compass.ts index d20366cc403..6bdb804e20b 100644 --- a/packages/compass-e2e-tests/helpers/compass.ts +++ b/packages/compass-e2e-tests/helpers/compass.ts @@ -8,6 +8,7 @@ import { execFile } from 'child_process'; import type { ExecFileOptions, ExecFileException } from 'child_process'; import { promisify } from 'util'; import zlib from 'zlib'; +import type { RemoteOptions } from 'webdriverio'; import { remote } from 'webdriverio'; import { rebuild } from '@electron/rebuild'; import type { RebuildOptions } from '@electron/rebuild'; @@ -24,6 +25,29 @@ import Debug from 'debug'; import semver from 'semver'; import crossSpawn from 'cross-spawn'; import { CHROME_STARTUP_FLAGS } from './chrome-startup-flags'; +import { + DEFAULT_CONNECTION_STRINGS, + DEFAULT_CONNECTION_NAMES, + DEFAULT_CONNECTIONS_SERVER_INFO, + ELECTRON_CHROMIUM_VERSION, + TEST_COMPASS_WEB as _TEST_COMPASS_WEB, + LOG_PATH, + LOG_COVERAGE_PATH, + COMPASS_DESKTOP_PATH, + LOG_OUTPUT_PATH, + LOG_SCREENSHOTS_PATH, + WEBDRIVER_DEFAULT_WAITFOR_TIMEOUT, + WEBDRIVER_DEFAULT_WAITFOR_INTERVAL, + TEST_COMPASS_DESKTOP_PACKAGED_APP, + ELECTRON_PATH, + COMPASS_WEB_BROWSER_NAME, + COMPASS_WEB_BROWSER_VERSION, + TEST_ATLAS_CLOUD_EXTERNAL, + TEST_ATLAS_CLOUD_EXTERNAL_COOKIES_FILE, + TEST_ATLAS_CLOUD_EXTERNAL_URL, + TEST_ATLAS_CLOUD_EXTERNAL_GROUP_ID, + COMPASS_WEB_SANDBOX_URL, +} from './test-runner-context'; const debug = Debug('compass-e2e-tests'); @@ -32,24 +56,8 @@ const { Z_SYNC_FLUSH } = zlib.constants; const packageCompassAsync = promisify(packageCompass); -export const COMPASS_PATH = path.dirname( - require.resolve('mongodb-compass/package.json') -); -export const LOG_PATH = path.resolve(__dirname, '..', '.log'); -const OUTPUT_PATH = path.join(LOG_PATH, 'output'); -export const SCREENSHOTS_PATH = path.join(LOG_PATH, 'screenshots'); -const COVERAGE_PATH = path.join(LOG_PATH, 'coverage'); - -let MONGODB_VERSION = ''; -let MONGODB_USE_ENTERPRISE = - (process.env.MONGODB_VERSION?.endsWith('-enterprise') && 'yes') ?? 'no'; - // should we test compass-web (true) or compass electron (false)? -export const TEST_COMPASS_WEB = process.argv.includes('--test-compass-web'); -export const TEST_ATLAS_CLOUD_EXTERNAL_URL = - process.env.TEST_ATLAS_CLOUD_EXTERNAL_URL; -export const TEST_ATLAS_CLOUD_EXTERNAL_GROUP_ID = - process.env.TEST_ATLAS_CLOUD_EXTERNAL_GROUP_ID; +export const TEST_COMPASS_WEB = _TEST_COMPASS_WEB; // multiple connections is now the default export const TEST_MULTIPLE_CONNECTIONS = true; @@ -69,83 +77,31 @@ export function skipForWeb( } } -function getBrowserName() { - return process.env.BROWSER_NAME ?? 'chrome'; -} - -export const BROWSER_NAME = getBrowserName(); - export const MONGODB_TEST_SERVER_PORT = Number( process.env.MONGODB_TEST_SERVER_PORT ?? 27091 ); -export const DEFAULT_CONNECTION_STRING_1 = - process.env.TEST_ATLAS_CLOUD_EXTERNAL_CONNECTION_STRING_1 || - `mongodb://127.0.0.1:${MONGODB_TEST_SERVER_PORT}/test`; +export const DEFAULT_CONNECTION_STRING_1 = DEFAULT_CONNECTION_STRINGS[0]; // NOTE: in browser.setupDefaultConnections() we don't give the first connection an // explicit name, so it gets a calculated one based off the connection string -export const DEFAULT_CONNECTION_NAME_1 = - process.env.TEST_ATLAS_CLOUD_EXTERNAL_CONNECTION_NAME_1 || - connectionNameFromString(DEFAULT_CONNECTION_STRING_1); +export const DEFAULT_CONNECTION_NAME_1 = DEFAULT_CONNECTION_NAMES[0]; // for testing multiple connections -export const DEFAULT_CONNECTION_STRING_2 = - process.env.TEST_ATLAS_CLOUD_EXTERNAL_CONNECTION_STRING_2 || - `mongodb://127.0.0.1:${MONGODB_TEST_SERVER_PORT + 1}/test`; +export const DEFAULT_CONNECTION_STRING_2 = DEFAULT_CONNECTION_STRINGS[1]; // NOTE: in browser.setupDefaultConnections() the second connection gets given an explicit name -export const DEFAULT_CONNECTION_NAME_2 = - process.env.TEST_ATLAS_CLOUD_EXTERNAL_CONNECTION_NAME_2 || 'connection-2'; - -export function updateMongoDBServerInfo() { - try { - const { stdout, stderr } = crossSpawn.sync( - 'npm', - [ - 'run', - '--silent', - /** - * The server info update is done through a separate script and not by - * using a MongoClient directly because doing so causes an unexplainable - * segfault crash in e2e-coverage task in evergreen CI. Moving this - * logic to a separate script seems to solve this problem, but if at any - * point the issue returns, feel free to revert this whole change - **/ - 'server-info', - '--', - '--connectionString', - DEFAULT_CONNECTION_STRING_1, - ], - { encoding: 'utf-8' } - ); - if (stderr?.length) { - throw new Error(stderr); - } - const { version, enterprise } = JSON.parse(stdout); - MONGODB_VERSION = version; - MONGODB_USE_ENTERPRISE = enterprise ? 'yes' : 'no'; - debug( - `Got server info: v${String(version)} (${ - enterprise ? 'enterprise' : 'community' - })` - ); - } catch (err) { - (err as Error).message = - 'Failed trying to get MongoDB server info:\n\n' + (err as Error).message; - throw err; - } -} +export const DEFAULT_CONNECTION_NAME_2 = DEFAULT_CONNECTION_NAMES[1]; export const serverSatisfies = ( semverCondition: string, enterpriseExact?: boolean ) => { + const { version, enterprise } = DEFAULT_CONNECTIONS_SERVER_INFO[0]; return ( - semver.satisfies(MONGODB_VERSION, semverCondition, { + semver.satisfies(version, semverCondition, { includePrerelease: true, }) && (typeof enterpriseExact === 'boolean' - ? (enterpriseExact && MONGODB_USE_ENTERPRISE === 'yes') || - (!enterpriseExact && MONGODB_USE_ENTERPRISE !== 'yes') + ? (enterpriseExact && enterprise) || (!enterpriseExact && !enterprise) : true) ); }; @@ -398,13 +354,13 @@ export class Compass { }); if (coverage.main) { await fs.writeFile( - path.join(COVERAGE_PATH, `main.${this.name}.log`), + path.join(LOG_COVERAGE_PATH, `main.${this.name}.log`), coverage.main ); } if (coverage.renderer) { await fs.writeFile( - path.join(COVERAGE_PATH, `renderer.${this.name}.log`), + path.join(LOG_COVERAGE_PATH, `renderer.${this.name}.log`), coverage.renderer ); } @@ -482,13 +438,10 @@ async function getCompassExecutionParameters(): Promise<{ testPackagedApp: boolean; binary: string; }> { - const testPackagedApp = ['1', 'true'].includes( - process.env.TEST_PACKAGED_APP ?? '' - ); + const testPackagedApp = TEST_COMPASS_DESKTOP_PACKAGED_APP; const binary = testPackagedApp ? getCompassBinPath(await getCompassBuildMetadata()) - : // eslint-disable-next-line @typescript-eslint/no-var-requires - (require('electron') as unknown as string); + : ELECTRON_PATH; return { testPackagedApp, binary }; } @@ -516,7 +469,7 @@ export async function runCompassOnce(args: string[], timeout = 30_000) { const { binary } = await getCompassExecutionParameters(); debug('spawning compass...', { binary, - COMPASS_PATH, + COMPASS_DESKTOP_PATH, defaultUserDataDir, args, timeout, @@ -529,7 +482,7 @@ export async function runCompassOnce(args: string[], timeout = 30_000) { const { error, stdout, stderr } = await execFileIgnoreError( binary, [ - COMPASS_PATH, + COMPASS_DESKTOP_PATH, // When running binary without webdriver, we need to pass the same flags // as we pass when running with webdriverio to have similar behaviour. ...CHROME_STARTUP_FLAGS, @@ -592,9 +545,9 @@ async function processCommonOpts({ // for consistency let's mkdir for both of them just in case await fs.mkdir(path.dirname(chromedriverLogPath), { recursive: true }); await fs.mkdir(webdriverLogPath, { recursive: true }); - await fs.mkdir(OUTPUT_PATH, { recursive: true }); - await fs.mkdir(SCREENSHOTS_PATH, { recursive: true }); - await fs.mkdir(COVERAGE_PATH, { recursive: true }); + await fs.mkdir(LOG_OUTPUT_PATH, { recursive: true }); + await fs.mkdir(LOG_SCREENSHOTS_PATH, { recursive: true }); + await fs.mkdir(LOG_COVERAGE_PATH, { recursive: true }); // https://webdriver.io/docs/options/#webdriver-options const webdriverOptions = { @@ -604,14 +557,8 @@ async function processCommonOpts({ // https://webdriver.io/docs/options/#webdriverio const wdioOptions = { - // default is 3000ms - waitforTimeout: process.env.COMPASS_TEST_DEFAULT_WAITFOR_TIMEOUT - ? Number(process.env.COMPASS_TEST_DEFAULT_WAITFOR_TIMEOUT) - : 120_000, // shorter than the test timeout so the exact line will fail, not the test - // default is 500ms - waitforInterval: process.env.COMPASS_TEST_DEFAULT_WAITFOR_INTERVAL - ? Number(process.env.COMPASS_TEST_DEFAULT_WAITFOR_INTERVAL) - : 100, + waitforTimeout: WEBDRIVER_DEFAULT_WAITFOR_TIMEOUT, + waitforInterval: WEBDRIVER_DEFAULT_WAITFOR_INTERVAL, }; process.env.DEBUG = `${process.env.DEBUG ?? ''},mongodb-compass:main:logging`; @@ -645,7 +592,7 @@ async function startCompassElectron( if (!testPackagedApp) { // https://www.electronjs.org/docs/latest/tutorial/automated-testing#with-webdriverio - chromeArgs.push(`--app=${COMPASS_PATH}`); + chromeArgs.push(`--app=${COMPASS_DESKTOP_PATH}`); } if (opts.firstRun === false) { @@ -692,7 +639,7 @@ async function startCompassElectron( automationProtocol: 'webdriver' as const, capabilities: { browserName: 'chromium', - browserVersion: process.env.CHROME_VERSION, + browserVersion: ELECTRON_CHROMIUM_VERSION, // https://chromedriver.chromium.org/capabilities#h.p_ID_106 'goog:chromeOptions': { binary: maybeWrappedBinary, @@ -786,47 +733,52 @@ export async function startBrowser( runCounter++; const { webdriverOptions, wdioOptions } = await processCommonOpts(); - const browser: CompassBrowser = (await remote({ + const options: RemoteOptions = { capabilities: { - browserName: BROWSER_NAME, // 'chrome' or 'firefox' - // https://webdriver.io/docs/driverbinaries/ - // If you leave out browserVersion it will try and find the browser binary - // on your system. If you specify it it will download that version. The - // main limitation then is that 'latest' is the only 'semantic' version - // that is supported for Firefox. - // https://github.com/puppeteer/puppeteer/blob/ab5d4ac60200d1cea5bcd4910f9ccb323128e79a/packages/browsers/src/browser-data/browser-data.ts#L66 - // Alternatively we can download it ourselves and specify the path to the - // binary or we can even start and stop chromedriver/geckodriver manually. - // NOTE: The version of chromedriver or geckodriver in play might also be - // relevant. - browserVersion: 'latest', + browserName: COMPASS_WEB_BROWSER_NAME, + ...(COMPASS_WEB_BROWSER_VERSION && { + browserVersion: COMPASS_WEB_BROWSER_VERSION, + }), }, ...webdriverOptions, ...wdioOptions, - })) as CompassBrowser; + }; - if (TEST_ATLAS_CLOUD_EXTERNAL_URL) { - // Navigate to a 404 page to set cookies - await browser.navigateTo(`https://${TEST_ATLAS_CLOUD_EXTERNAL_URL}/404`); + debug('Starting browser via webdriverio with the following configuration:'); + debug(JSON.stringify(options, null, 2)); + + const browser: CompassBrowser = (await remote(options)) as CompassBrowser; + + if (TEST_ATLAS_CLOUD_EXTERNAL) { + // To be able to use `setCookies` method, we need to first open any page on + // the same domain as the cookies we are going to set + // https://webdriver.io/docs/api/browser/setCookies/ + await browser.navigateTo(`${TEST_ATLAS_CLOUD_EXTERNAL_URL!}/404`); + + type StoredAtlasCloudCookies = { + name: string; + value: string; + domain: string; + path: string; + secure: boolean; + httpOnly: boolean; + expirationDate: number; + }[]; - const cookiesFile = process.env.TEST_ATLAS_CLOUD_EXTERNAL_COOKIES_FILE; - if (!cookiesFile) { - throw new Error( - 'TEST_ATLAS_CLOUD_EXTERNAL_URL is set but TEST_ATLAS_CLOUD_EXTERNAL_COOKIES_FILE is not. Please set TEST_ATLAS_CLOUD_EXTERNAL_COOKIES_FILE to the path of the cookies file.' - ); - } const cookies: StoredAtlasCloudCookies = JSON.parse( - await fs.readFile(cookiesFile, 'utf8') + await fs.readFile(TEST_ATLAS_CLOUD_EXTERNAL_COOKIES_FILE!, 'utf8') ); - // These are the relevant cookies for auth: - // https://github.com/10gen/mms/blob/6d27992a6ab9ab31471c8bcdaa4e347aa39f4013/server/src/features/com/xgen/svc/cukes/helpers/Client.java#L122-L130 await browser.setCookies( cookies .filter((cookie) => { - cookie.name.includes('mmsa-') || + // These are the relevant cookies for auth: + // https://github.com/10gen/mms/blob/6d27992a6ab9ab31471c8bcdaa4e347aa39f4013/server/src/features/com/xgen/svc/cukes/helpers/Client.java#L122-L130 + return ( + cookie.name.includes('mmsa-') || cookie.name.includes('mdb-sat') || - cookie.name.includes('mdb-srt'); + cookie.name.includes('mdb-srt') + ); }) .map((cookie) => ({ name: cookie.name, @@ -839,10 +791,10 @@ export async function startBrowser( ); await browser.navigateTo( - `https://${TEST_ATLAS_CLOUD_EXTERNAL_URL}/v2/${TEST_ATLAS_CLOUD_EXTERNAL_GROUP_ID}#/explorer` + `${TEST_ATLAS_CLOUD_EXTERNAL_URL!}/v2/${TEST_ATLAS_CLOUD_EXTERNAL_GROUP_ID!}#/explorer` ); } else { - await browser.navigateTo('http://localhost:7777/'); + await browser.navigateTo(COMPASS_WEB_SANDBOX_URL); } const compass = new Compass(name, browser, { @@ -923,7 +875,7 @@ function formattedDate(): string { } export async function rebuildNativeModules( - compassPath = COMPASS_PATH + compassPath = COMPASS_DESKTOP_PATH ): Promise { const fullCompassPath = require.resolve( path.join(compassPath, 'package.json') @@ -950,7 +902,7 @@ export async function rebuildNativeModules( } export async function compileCompassAssets( - compassPath = COMPASS_PATH + compassPath = COMPASS_DESKTOP_PATH ): Promise { await promisify(execFile)('npm', ['run', 'compile'], { cwd: compassPath }); } @@ -982,7 +934,7 @@ async function getCompassBuildMetadata(): Promise { export async function buildCompass( force = false, - compassPath = COMPASS_PATH + compassPath = COMPASS_DESKTOP_PATH ): Promise { if (!force) { try { @@ -1097,7 +1049,7 @@ export async function init( await browser.execute(() => { // eslint-disable-next-line @typescript-eslint/no-var-requires const { ipcRenderer } = require('electron'); - ipcRenderer.invoke('compass:maximize'); + void ipcRenderer.invoke('compass:maximize'); }); } @@ -1173,7 +1125,7 @@ export function screenshotPathName(text: string) { * @param {string} filename */ export function outputFilename(filename: string): string { - return path.join(OUTPUT_PATH, filename); + return path.join(LOG_OUTPUT_PATH, filename); } export async function screenshotIfFailed( diff --git a/packages/compass-e2e-tests/helpers/gunzip.ts b/packages/compass-e2e-tests/helpers/gunzip.ts new file mode 100755 index 00000000000..8777c224483 --- /dev/null +++ b/packages/compass-e2e-tests/helpers/gunzip.ts @@ -0,0 +1,41 @@ +import Debug from 'debug'; +import { glob as globAsync } from 'glob'; +import { createReadStream, createWriteStream } from 'fs'; +import { pipeline } from 'stream/promises'; +import { createGunzip } from 'zlib'; +import path from 'path'; + +const debug = Debug('compass-e2e-tests:gunzip'); + +async function gunzip(input: string, output: string, signal: AbortSignal) { + const readStream = createReadStream(input); + const gunzip = createGunzip(); + const writeStream = createWriteStream(output); + try { + await pipeline(readStream, gunzip, writeStream, { signal, end: true }); + } catch (err) { + if (signal.aborted) { + return; + } + throw err; + } +} + +async function run(glob: string, cwd: string, signal: AbortSignal) { + const filenames = (await globAsync(glob, { cwd })).map((filepath) => { + return path.join(cwd, filepath); + }); + if (filenames.length === 0) { + throw new Error(`Failed to unpack ${glob} at ${cwd}: no files found`); + } + debug('Unpacking following files:', filenames); + for (const input of filenames) { + if (signal.aborted) { + return; + } + const output = input.replace(/\.gz$/, ''); + await gunzip(input, output, signal); + } +} + +export default run; diff --git a/packages/compass-e2e-tests/helpers/insert-data.ts b/packages/compass-e2e-tests/helpers/insert-data.ts index 8a2cdd18a2f..2a18bdb218b 100644 --- a/packages/compass-e2e-tests/helpers/insert-data.ts +++ b/packages/compass-e2e-tests/helpers/insert-data.ts @@ -46,7 +46,7 @@ for (let i = 0; i < 26; ++i) { let clients: MongoClient[]; let test_dbs: Db[]; -before(async () => { +export const beforeAll = async () => { // Insert data on both connections so that the same databases and collections // will exist on both servers and then anything that's not properly scoped to // the correct connection has a chance to operate on the wrong one and @@ -70,13 +70,13 @@ before(async () => { ); test_dbs = clients.map((client) => client.db('test')); -}); +}; -after(async () => { +export const afterAll = async () => { await Promise.all(clients.map((client) => client.close())); -}); +}; -beforeEach(async () => { +export const beforeEach = async () => { // Drop the databases that get created by tests or the functions below const promises = []; @@ -87,7 +87,13 @@ beforeEach(async () => { } await Promise.all(promises); -}); +}; + +export const mochaRootHooks: Mocha.RootHookObject = { + beforeAll, + beforeEach, + afterAll, +}; export async function createDummyCollections(): Promise { const promises = []; diff --git a/packages/compass-e2e-tests/helpers/result-logger.ts b/packages/compass-e2e-tests/helpers/result-logger.ts deleted file mode 100644 index ba5d65813b4..00000000000 --- a/packages/compass-e2e-tests/helpers/result-logger.ts +++ /dev/null @@ -1,340 +0,0 @@ -import assert from 'assert'; -import Debug from 'debug'; -import Mocha from 'mocha'; -import type { MongoClient, Collection } from 'mongodb'; -import type { ObjectId } from 'bson'; - -const debug = Debug('result-logger'); - -const DB_NAME = 'compass_e2e'; -const COLLECTION_NAME = 'results'; - -const { - EVENT_HOOK_BEGIN, - EVENT_HOOK_END, - EVENT_TEST_BEGIN, - EVENT_TEST_FAIL, - EVENT_TEST_PASS, -} = Mocha.Runner.constants; - -// env vars to store with the metrics. Be careful not to include secrets. -const ENV_VARS = [ - 'CI', - - // evergreen - 'EVERGREEN_AUTHOR', - 'EVERGREEN_BRANCH_NAME', - 'EVERGREEN_BUILD_ID', - 'EVERGREEN_BUILD_VARIANT', - 'EVERGREEN_EXECUTION', - 'EVERGREEN_IS_PATCH', - 'EVERGREEN_PROJECT', - 'EVERGREEN_REVISION', - 'EVERGREEN_TASK_ID', - 'EVERGREEN_TASK_NAME', - 'EVERGREEN_TASK_URL', - 'EVERGREEN_VERSION_ID', - 'EVERGREEN_WORKDIR', - - // github - 'GITHUB_WORKFLOW', - 'GITHUB_RUN_ID', - 'GITHUB_RUN_NUMBER', - 'GITHUB_JOB', - 'GITHUB_ACTION', - 'GITHUB_ACTION_PATH', - 'GITHUB_ACTIONS', - 'GITHUB_ACTOR', - 'GITHUB_REPOSITORY', - 'GITHUB_EVENT_NAME', - 'GITHUB_EVENT_PATH', - 'GITHUB_WORKSPACE', - 'GITHUB_SHA', - 'GITHUB_REF', - 'GITHUB_HEAD_REF', - 'GITHUB_BASE_REF', - 'GITHUB_SERVER_URL', - 'GITHUB_API_URL', - 'GITHUB_GRAPHQL_URL', - 'RUNNER_NAME', - 'RUNNER_OS', - 'RUNNER_TEMP', - 'RUNNER_TOOL_CACHE', -] as const; - -type Env = { [K in typeof ENV_VARS[number]]?: string }; - -type HookOrTest = Mocha.Hook | Mocha.Test; - -function joinPath(parts: string[]) { - // Turn an array of test/hook path components into a string we can use as an - // identifier for the test, hook or suite - - return parts.join(' / '); -} - -function githubWorkflowRunUrl() { - const serverURL = process.env.GITHUB_SERVER_URL ?? ''; - const repository = process.env.GITHUB_REPOSITORY ?? ''; - const runID = process.env.GITHUB_RUN_ID ?? ''; - - return `${serverURL}/${repository}/actions/runs/${runID}`; -} - -type Result = { - test_file: string; - type?: string; // tests don't have types and we delete these before handing it to evergreen - start: number; - status: 'start' | 'pass' | 'fail' | 'silentfail'; - end?: number; - elapsed?: number; - error?: any; - task_id?: string; - execution?: number; -}; - -type Report = { - results: Result[]; -}; - -export default class ResultLogger { - _id?: ObjectId; - start?: number; - end?: number; - elapsed?: number; - client?: MongoClient; - collection?: Collection; - context: { - env: Env; - ci: 'evergreen' | 'github-actions' | 'unknown'; - platform: string; - os: string; - author: string; - branch: string; - commit: string; - url: string; - }; - results: Result[]; - runner: Mocha.Runner; - - constructor(client: MongoClient, runner: Mocha.Runner) { - if (client) { - debug(`Logging E2E test metrics to ${DB_NAME}.${COLLECTION_NAME}`); - // client can be undefined if we don't want to write to the db - this.client = client; - const db = this.client.db(DB_NAME); - this.collection = db.collection(COLLECTION_NAME); - } - - this.context = { - // copy known env vars as-is if they are set - env: ENV_VARS.reduce((obj: Env, name: keyof Env) => { - const value = process.env[name]; - if (value) { - obj[name] = value; - } - return obj; - }, {}), - - // infer some common variables - ci: process.env.EVERGREEN - ? 'evergreen' - : process.env.GITHUB_ACTIONS - ? 'github-actions' - : 'unknown', - - platform: process.platform, - - // this way we should be able to distinguish between ubuntu and rhel on - // evergreen and linux on github actions - os: - process.env.EVERGREEN_BUILD_VARIANT ?? - process.env.RUNNER_OS ?? - 'unknown', - - author: - process.env.EVERGREEN_AUTHOR ?? process.env.GITHUB_ACTOR ?? 'unknown', - - // For an evergreen patch the branch name is set to main which is not what we want - branch: process.env.EVERGREEN_IS_PATCH - ? 'evergreen-patch' - : process.env.EVERGREEN_BRANCH_NAME ?? - process.env.GITHUB_HEAD_REF ?? - 'unknown', - - // EVERGREEN_REVISION is the ${revision} expansion, but the ${github_commit} one might be better? - // GITHUB_SHA also doesn't look 100% right. - commit: - process.env.EVERGREEN_REVISION ?? process.env.GITHUB_SHA ?? 'unknown', - - url: process.env.EVERGREEN - ? process.env.EVERGREEN_TASK_URL ?? '' - : process.env.GITHUB_ACTIONS - ? githubWorkflowRunUrl() - : 'unknown', - }; - - // Hooks and tests. See - // https://github.com/evergreen-ci/evergreen/wiki/Project-Commands#attach-results - // for the target structure. - this.results = []; - - this.runner = runner; - - runner.on(EVENT_HOOK_BEGIN, (hook) => { - this.startResult(hook); - }); - - runner.on(EVENT_HOOK_END, (hook) => { - // unlike for tests, with hooks end only fires when it passes - this.passResult(hook); - }); - - runner.on(EVENT_TEST_BEGIN, (test) => { - this.startResult(test); - }); - - runner.on(EVENT_TEST_PASS, (test) => { - this.passResult(test); - }); - - runner.on(EVENT_TEST_FAIL, (hookOrTest: HookOrTest, error: any) => { - // tests and hooks failing go to the same event - if (hookOrTest.type === 'hook') { - // NOTE: if this is a beforeEach hook, then the test's EVENT_TEST_BEGIN - // will have fired but it will never get a corresponding - // EVENT_TEST_FAIL, leaving it stuck in the start state - this.failResult(hookOrTest, error); - } else { - this.failResult(hookOrTest, error); - } - }); - } - - async init(): Promise { - debug('init'); - - this.start = Date.now() / 1000; - if (this.collection) { - const { insertedId } = await this.collection.insertOne({ - ...this.context, - start: this.start, - status: 'start', - }); - this._id = insertedId; - debug('resultId', this._id); - } - } - - startResult(hookOrTest: HookOrTest): void { - const test_file = joinPath(hookOrTest.titlePath()); - debug('start', test_file); - - const result = { - test_file, - type: hookOrTest.type, - start: Date.now() / 1000, - status: 'start', // evergreen only knows fail, pass, silentfail and skip - } as Result; - - this.results.push(result); - } - - passResult(hookOrTest: HookOrTest): void { - const test_file = joinPath(hookOrTest.titlePath()); - debug('pass', test_file); - const result = this.findResult(test_file); - - assert.ok(result); - - result.status = 'pass'; - result.end = Date.now() / 1000; - result.elapsed = result.end - result.start; - } - - failResult(hookOrTest: HookOrTest, error: Error): void { - const test_file = joinPath(hookOrTest.titlePath()); - debug('fail', test_file); - const result = this.findResult(test_file); - - assert.ok(result); - - result.status = 'fail'; - result.end = Date.now() / 1000; - result.elapsed = result.end - result.start; - result.error = error.stack; - } - - async done(failures: number): Promise { - debug('done'); - - this.end = Date.now() / 1000; - this.elapsed = this.end - (this.start || 0); // typescript thinks start might be undefined - - if (this.collection) { - const update = { - results: this.results, - elapsed: this.elapsed, - status: failures ? 'fail' : 'pass', - failures, - }; - - await this.collection.updateOne({ _id: this._id }, { $set: update }); - } - - return this.report(); - } - - report(): Report { - const results = this.results - .filter((r) => { - if (r.status !== 'pass') { - // keep all errors - return true; - } - // strip out passed hooks because it is a bit noisy - if (r.type === 'hook') { - return false; - } - return true; - }) - .map((r) => { - const result = { ...r }; - // change things that are still stuck as "start" to something evergreen - // understands - if (result.status === 'start') { - result.status = 'silentfail'; - } - - // copy over some evergreen-specific fields if they exist - if (process.env.EVERGREEN_TASK_ID) { - result.task_id = process.env.EVERGREEN_TASK_ID; - } - if (process.env.EVERGREEN_EXECUTION) { - result.execution = parseInt(process.env.EVERGREEN_EXECUTION, 10); - } - - if (result.type) { - delete result.type; - } - - // only include fields that evergreen knows about - // https://github.com/evergreen-ci/evergreen/wiki/Project-Commands#attach-results - delete result.error; - - return result; - }); - - return { results }; - } - - findResult(test_file: string): Result | null { - for (const result of this.results) { - if (result.test_file === test_file) { - return result; - } - } - - return null; - } -} diff --git a/packages/compass-e2e-tests/helpers/test-runner-context.ts b/packages/compass-e2e-tests/helpers/test-runner-context.ts new file mode 100644 index 00000000000..3464c11bbbc --- /dev/null +++ b/packages/compass-e2e-tests/helpers/test-runner-context.ts @@ -0,0 +1,193 @@ +import { + getConnectionTitle, + type ConnectionInfo, +} from '@mongodb-js/connection-info'; +import path from 'path'; +import electronPath from 'electron'; +import electronPackageJson from 'electron/package.json'; +// @ts-expect-error no types for this package +import { electronToChromium } from 'electron-to-chromium'; +import type { MongoClusterOptions } from 'mongodb-runner'; + +if (typeof electronPath !== 'string') { + throw new Error( + 'Running e2e tests in an unsupported runtime: `electronPath` is not a string' + ); +} + +// TODO: Probably time to use some arg parser for this already +export const ALLOWED_RUNNER_ARGS = [ + '--test-compass-web', + '--no-compile', + '--no-native-modules', + '--test-packaged-app', + '--disable-start-stop', + '--bail', +]; + +/** + * Variables used by a special use-case of running e2e tests against a + * cloud(-dev).mongodb.com URL. If you're changing anything related to these, + * make sure that the tests in mms are also updated to account for that + */ +export const TEST_ATLAS_CLOUD_EXTERNAL_URL = + process.env.TEST_ATLAS_CLOUD_EXTERNAL_URL; +export const TEST_ATLAS_CLOUD_EXTERNAL_COOKIES_FILE = + process.env.TEST_ATLAS_CLOUD_EXTERNAL_COOKIES_FILE; +export const TEST_ATLAS_CLOUD_EXTERNAL_GROUP_ID = + process.env.TEST_ATLAS_CLOUD_EXTERNAL_GROUP_ID; +const TEST_ATLAS_CLOUD_EXTERNAL_DEFAULT_CONNECTIONS: ConnectionInfo[] | null = + JSON.parse(process.env.TEST_ATLAS_CLOUD_DEFAULT_CONNECTIONS ?? 'null'); + +const ALL_ATLAS_CLOUD_EXTERNAL_VARS = [ + TEST_ATLAS_CLOUD_EXTERNAL_URL, + TEST_ATLAS_CLOUD_EXTERNAL_COOKIES_FILE, + TEST_ATLAS_CLOUD_EXTERNAL_GROUP_ID, + TEST_ATLAS_CLOUD_EXTERNAL_DEFAULT_CONNECTIONS, +]; + +export const TEST_ATLAS_CLOUD_EXTERNAL = ALL_ATLAS_CLOUD_EXTERNAL_VARS.some( + (val) => { + return !!val; + } +); + +if ( + TEST_ATLAS_CLOUD_EXTERNAL && + ALL_ATLAS_CLOUD_EXTERNAL_VARS.some((val) => { + return !val; + }) +) { + throw new Error( + 'Trying to test Atlas Cloud external URL but some required variables are missing' + ); +} + +export const TEST_COMPASS_WEB = + process.argv.includes('--test-compass-web') || TEST_ATLAS_CLOUD_EXTERNAL; +export const TEST_COMPASS_DESKTOP = !TEST_COMPASS_WEB; +export const TEST_COMPASS_DESKTOP_PACKAGED_APP = process.argv.includes( + '--test-packaged-app' +); +// Skip this step if you are running tests consecutively and don't need to +// rebuild modules all the time. Also no need to ever recompile when testing +// compass-web. +export const SKIP_COMPASS_DESKTOP_COMPILE = + process.argv.includes('--no-compile') && !TEST_COMPASS_WEB; +// Skip this step if you want to run tests against your own compilation (e.g, a +// dev build or a build running in watch mode that autorecompiles). Also no need +// to recompile when testing compass-web. +export const SKIP_NATIVE_MODULE_REBUILD = + process.argv.includes('--no-native-modules') && !TEST_COMPASS_WEB; +export const DISABLE_START_STOP = process.argv.includes('--disable-start-stop'); +export const MOCHA_BAIL = process.argv.includes('--bail'); + +export const COMPASS_WEB_BROWSER_NAME = + process.env.COMPASS_WEB_BROWSER_NAME ?? 'chrome'; +// https://webdriver.io/docs/driverbinaries/ +// +// If you leave out browserVersion it will try and find the browser binary on +// your system. If you specify it it will download that version. The main +// limitation then is that 'latest' is the only 'semantic' version that is +// supported for Firefox. +// https://github.com/puppeteer/puppeteer/blob/ab5d4ac60200d1cea5bcd4910f9ccb323128e79a/packages/browsers/src/browser-data/browser-data.ts#L66 +// +// Alternatively we can download it ourselves and specify the path to the binary +// or we can even start and stop chromedriver/geckodriver manually. +// +// NOTE: The version of chromedriver or geckodriver in play might also be +// relevant. +export const COMPASS_WEB_BROWSER_VERSION = + process.env.COMPASS_WEB_BROWSER_VERSION === 'unset' + ? undefined + : process.env.BROWSER_VERSION ?? 'latest'; +export const COMPASS_WEB_SANDBOX_URL = 'http://localhost:7777'; + +const MONGODB_TESTSERVER_VERSION = + process.env.MONGODB_VERSION ?? process.env.MONGODB_RUNNER_VERSION; + +export const DEFAULT_CONNECTIONS: (ConnectionInfo & { + testServer?: Partial; +})[] = + TEST_ATLAS_CLOUD_EXTERNAL && TEST_ATLAS_CLOUD_EXTERNAL_DEFAULT_CONNECTIONS + ? TEST_ATLAS_CLOUD_EXTERNAL_DEFAULT_CONNECTIONS + : [ + { + id: 'test-connection-1', + connectionOptions: { + connectionString: 'mongodb://127.0.0.1:27091/test', + }, + testServer: { + version: MONGODB_TESTSERVER_VERSION, + topology: 'replset', + secondaries: 0, + args: ['--port', '27091'], + }, + }, + { + id: 'test-connection-2', + connectionOptions: { + connectionString: 'mongodb://127.0.0.1:27092/test', + }, + favorite: { + name: 'connection-2', + color: 'Iris', + }, + testServer: { + version: MONGODB_TESTSERVER_VERSION, + topology: 'replset', + secondaries: 0, + args: ['--port', '27092'], + }, + }, + ]; + +export const DEFAULT_CONNECTION_STRINGS = DEFAULT_CONNECTIONS.map((info) => { + return info.connectionOptions.connectionString; +}); + +export const DEFAULT_CONNECTION_NAMES = DEFAULT_CONNECTIONS.map((info) => { + return getConnectionTitle(info); +}); + +export const DEFAULT_CONNECTIONS_SERVER_INFO: { + version: string; + enterprise: boolean; +}[] = []; + +export const E2E_WORKSPACE_PATH = path.dirname( + require.resolve('compass-e2e-tests/package.json') +); +// /packages/compass-e2e-tests +// /packages +// +export const MONOREPO_ROOT_PATH = path.resolve(E2E_WORKSPACE_PATH, '..', '..'); +export const COMPASS_DESKTOP_PATH = path.dirname( + require.resolve('mongodb-compass/package.json') +); +export const COMPASS_WEB_PATH = path.dirname( + require.resolve('@mongodb-js/compass-web/package.json') +); +export const LOG_PATH = path.resolve(E2E_WORKSPACE_PATH, '.log'); +export const LOG_OUTPUT_PATH = path.join(LOG_PATH, 'output'); +export const LOG_SCREENSHOTS_PATH = path.join(LOG_PATH, 'screenshots'); +export const LOG_COVERAGE_PATH = path.join(LOG_PATH, 'coverage'); +// Set coverage to the root of the monorepo so it will be generated for +// everything and not just packages/compass +export const COVERAGE_PATH = (process.env.COVERAGE = MONOREPO_ROOT_PATH); + +export const ELECTRON_PATH = electronPath; +export const ELECTRON_VERSION = electronPackageJson.version; +export const ELECTRON_CHROMIUM_VERSION = electronToChromium(ELECTRON_VERSION); + +export const WEBDRIVER_DEFAULT_WAITFOR_TIMEOUT = process.env + .COMPASS_TEST_DEFAULT_WAITFOR_TIMEOUT + ? Number(process.env.COMPASS_TEST_DEFAULT_WAITFOR_TIMEOUT) + : 120_000; // default is 3000ms +export const WEBDRIVER_DEFAULT_WAITFOR_INTERVAL = process.env + .COMPASS_TEST_DEFAULT_WAITFOR_INTERVAL + ? Number(process.env.COMPASS_TEST_DEFAULT_WAITFOR_INTERVAL) + : 100; // default is 500ms +// Kinda arbitrary, but longer than WEBDRIVER_DEFAULT_WAITFOR_TIMEOUT so the +// test can fail before Mocha times out +export const MOCHA_DEFAULT_TIMEOUT = WEBDRIVER_DEFAULT_WAITFOR_TIMEOUT * 2; diff --git a/packages/compass-e2e-tests/helpers/test-runner-global-fixtures.ts b/packages/compass-e2e-tests/helpers/test-runner-global-fixtures.ts new file mode 100644 index 00000000000..8fd8e030921 --- /dev/null +++ b/packages/compass-e2e-tests/helpers/test-runner-global-fixtures.ts @@ -0,0 +1,225 @@ +import gunzip from './gunzip'; +import fs from 'fs'; +import { + COMPASS_WEB_SANDBOX_URL, + DEFAULT_CONNECTIONS, + DEFAULT_CONNECTIONS_SERVER_INFO, + DISABLE_START_STOP, + E2E_WORKSPACE_PATH, + LOG_PATH, + SKIP_COMPASS_DESKTOP_COMPILE, + SKIP_NATIVE_MODULE_REBUILD, + TEST_ATLAS_CLOUD_EXTERNAL, + TEST_COMPASS_DESKTOP, + TEST_COMPASS_DESKTOP_PACKAGED_APP, + TEST_COMPASS_WEB, +} from './test-runner-context'; +import Debug from 'debug'; +import { startTestServer } from '@mongodb-js/compass-test-server'; +import crossSpawn from 'cross-spawn'; +import kill from 'tree-kill'; +import { MongoClient } from 'mongodb'; +import { isEnterprise } from 'mongodb-build-info'; +import { + buildCompass, + compileCompassAssets, + rebuildNativeModules, + removeUserDataDir, +} from './compass'; +import { getConnectionTitle } from '@mongodb-js/connection-info'; + +export const globalFixturesAbortController = new AbortController(); + +function throwIfAborted() { + if (globalFixturesAbortController.signal.aborted) { + throw new Error('Mocha run was aborted while global setup was in progress'); + } +} + +export let abortRunner: (() => void) | undefined; + +const debug = Debug('compass-e2e-tests:mocha-global-fixtures'); + +const wait = (ms: number) => + new Promise((resolve) => { + setTimeout(resolve, ms); + }); + +const cleanupFns: (() => Promise | void)[] = []; + +/** + * Main hook that does all the main pre-setup before running tests: + * - Unpacks the fixtures + * - Starts MongoDB servers for every default connection defined in + * DEFAULT_CONNECTIONS + * - Starts compass-web sandbox + * - Updates server metadata that will be used by tests + * - Compiles the desktop app + */ +export async function mochaGlobalSetup(this: Mocha.Runner) { + abortRunner = () => { + globalFixturesAbortController.abort(); + this.abort(); + }; + + try { + debug('Unzipping fixtures...'); + await gunzip( + // Not using absolute paths because Windows fails to resolve glob + // collectly in this case + 'fixtures/*.gz', + E2E_WORKSPACE_PATH, + globalFixturesAbortController.signal + ); + + throwIfAborted(); + + debug('X DISPLAY', process.env.DISPLAY); + + if (!DISABLE_START_STOP) { + for (const connectionInfo of DEFAULT_CONNECTIONS) { + if (connectionInfo.testServer) { + debug( + 'Starting MongoDB server for connection %s', + getConnectionTitle(connectionInfo) + ); + const server = await startTestServer(connectionInfo.testServer); + cleanupFns.push(() => { + debug( + 'Stopping server for connection %s', + getConnectionTitle(connectionInfo) + ); + return server.close(); + }); + } + throwIfAborted(); + } + + if (TEST_COMPASS_WEB && !TEST_ATLAS_CLOUD_EXTERNAL) { + debug('Starting Compass Web server ...'); + const compassWeb = spawnCompassWeb(); + cleanupFns.push(() => { + if (compassWeb.pid) { + debug(`Killing compass-web [${compassWeb.pid}]`); + kill(compassWeb.pid, 'SIGINT'); + } else { + debug('No pid for compass-web'); + } + }); + await waitForCompassWebToBeReady(); + } + } + + debug('Getting mongodb server info'); + await updateMongoDBServerInfo(); + + throwIfAborted(); + + try { + debug('Clearing out past logs'); + fs.rmdirSync(LOG_PATH, { recursive: true }); + } catch (e) { + debug('.log dir already removed'); + } + + fs.mkdirSync(LOG_PATH, { recursive: true }); + + if (TEST_COMPASS_DESKTOP) { + if (TEST_COMPASS_DESKTOP_PACKAGED_APP) { + debug('Building Compass before running the tests ...'); + await buildCompass(); + } else { + debug('Preparing Compass before running the tests'); + + if (!SKIP_NATIVE_MODULE_REBUILD) { + debug('Rebuilding native modules ...'); + await rebuildNativeModules(); + } + + if (!SKIP_COMPASS_DESKTOP_COMPILE) { + debug('Compiling Compass assets ...'); + await compileCompassAssets(); + } + } + } + + throwIfAborted(); + + cleanupFns.push(() => { + removeUserDataDir(); + }); + } catch (err) { + if (globalFixturesAbortController.signal.aborted) { + return; + } + throw err; + } +} + +export async function mochaGlobalTeardown() { + debug('Cleaning up after the tests ...'); + await Promise.allSettled( + cleanupFns.map((fn) => { + return fn(); + }) + ); +} + +function spawnCompassWeb() { + const proc = crossSpawn.spawn( + 'npm', + ['run', '--unsafe-perm', 'start', '--workspace', '@mongodb-js/compass-web'], + { + env: { + ...process.env, + OPEN_BROWSER: 'false', // tell webpack dev server not to open the default browser + DISABLE_DEVSERVER_OVERLAY: 'true', + APP_ENV: 'webdriverio', + }, + } + ); + proc.stdout.pipe(process.stdout); + proc.stderr.pipe(process.stderr); + return proc; +} + +async function waitForCompassWebToBeReady() { + let serverReady = false; + const start = Date.now(); + while (!serverReady) { + throwIfAborted(); + if (Date.now() - start >= 120_000) { + throw new Error( + 'The compass-web sandbox is still not running after 120000ms' + ); + } + try { + const res = await fetch(COMPASS_WEB_SANDBOX_URL); + serverReady = res.ok; + debug('Web server ready:', serverReady); + } catch (err) { + debug('Failed to connect to dev server:', (err as any).message); + } + await wait(1000); + } +} + +async function updateMongoDBServerInfo() { + try { + for (const { connectionOptions } of DEFAULT_CONNECTIONS) { + let client: MongoClient | undefined; + try { + client = new MongoClient(connectionOptions.connectionString); + const info = await client.db('admin').command({ buildInfo: 1 }); + DEFAULT_CONNECTIONS_SERVER_INFO.push({ + version: info.version, + enterprise: isEnterprise(info), + }); + } finally { + void client?.close(true); + } + } + } catch (err) { + debug('Failed to get MongoDB server info:', err); + } +} diff --git a/packages/compass-e2e-tests/index.ts b/packages/compass-e2e-tests/index.ts index 0974b80e50c..7efbb9710b8 100644 --- a/packages/compass-e2e-tests/index.ts +++ b/packages/compass-e2e-tests/index.ts @@ -1,266 +1,70 @@ #!/usr/bin/env ts-node import path from 'path'; -import fs from 'fs'; import { glob } from 'glob'; import crossSpawn from 'cross-spawn'; -import type { ChildProcessWithoutNullStreams } from 'child_process'; -// @ts-expect-error it thinks process does not have getActiveResourcesInfo -import { getActiveResourcesInfo } from 'process'; + import Mocha from 'mocha'; import Debug from 'debug'; -import type { MongoClient } from 'mongodb'; -import kill from 'tree-kill'; import { - rebuildNativeModules, - compileCompassAssets, - buildCompass, - COMPASS_PATH, - LOG_PATH, - removeUserDataDir, - updateMongoDBServerInfo, - TEST_ATLAS_CLOUD_EXTERNAL_URL, -} from './helpers/compass'; -import ResultLogger from './helpers/result-logger'; + ALLOWED_RUNNER_ARGS, + MOCHA_BAIL, + MOCHA_DEFAULT_TIMEOUT, +} from './helpers/test-runner-context'; +import { + abortRunner, + mochaGlobalSetup, + mochaGlobalTeardown, +} from './helpers/test-runner-global-fixtures'; +import { mochaRootHooks } from './helpers/insert-data'; +// @ts-expect-error no types for this package +import logRunning from 'why-is-node-running'; const debug = Debug('compass-e2e-tests'); -const wait = (ms: number) => - new Promise((resolve) => { - setTimeout(resolve, ms); - }); - -const allowedArgs = [ - '--test-compass-web', - '--no-compile', - '--no-native-modules', - '--test-packaged-app', - '--disable-start-stop', - '--bail', -]; - for (const arg of process.argv) { - if (arg.startsWith('--') && !allowedArgs.includes(arg)) { + if (arg.startsWith('--') && !ALLOWED_RUNNER_ARGS.includes(arg)) { throw Error( - `Unknown command argument "${arg}". Usage:\n\n npm run test ${allowedArgs - .map((arg) => `[${arg}]`) - .join(' ')}\n` + `Unknown command argument "${arg}". Usage:\n\n npm run test ${ALLOWED_RUNNER_ARGS.map( + (arg) => `[${arg}]` + ).join(' ')}\n` ); } } -// We can't import mongodb here yet because native modules will be recompiled -let metricsClient: MongoClient; - const FIRST_TEST = 'tests/time-to-first-query.test.ts'; -let compassWeb: ChildProcessWithoutNullStreams; - -async function setup() { - debug('X DISPLAY', process.env.DISPLAY); - - const disableStartStop = process.argv.includes('--disable-start-stop'); - const shouldTestCompassWeb = process.argv.includes('--test-compass-web'); - - if (!TEST_ATLAS_CLOUD_EXTERNAL_URL) { - // When working on the tests it is faster to just keep the server running. - if (!disableStartStop) { - debug('Starting MongoDB server'); - crossSpawn.sync('npm', ['run', 'start-servers'], { stdio: 'inherit' }); - - if (shouldTestCompassWeb) { - debug('Starting Compass Web'); - compassWeb = crossSpawn.spawn( - 'npm', - ['run', '--unsafe-perm', 'start-web'], - { - cwd: path.resolve(__dirname, '..', '..'), - env: { - ...process.env, - OPEN_BROWSER: 'false', // tell webpack dev server not to open the default browser - DISABLE_DEVSERVER_OVERLAY: 'true', - APP_ENV: 'webdriverio', - }, - } - ); - - compassWeb.stdout.pipe(process.stdout); - compassWeb.stderr.pipe(process.stderr); - - let serverReady = false; - const start = Date.now(); - while (!serverReady) { - if (Date.now() - start >= 120_000) { - throw new Error( - 'The compass-web sandbox is still not running after 120000ms' - ); - } - try { - const res = await fetch('http://localhost:7777'); - serverReady = res.ok; - debug('Web server ready: %s', serverReady); - } catch (err) { - debug('Failed to connect to dev server: %s', (err as any).message); - } - await wait(1000); - } - } else { - debug('Writing electron-versions.json'); - crossSpawn.sync('scripts/write-electron-versions.sh', [], { - stdio: 'inherit', - }); - } - } - } - - try { - debug('Clearing out past logs'); - fs.rmdirSync(LOG_PATH, { recursive: true }); - } catch (e) { - debug('.log dir already removed'); - } - - fs.mkdirSync(LOG_PATH, { recursive: true }); - - debug('Getting mongodb server info'); - updateMongoDBServerInfo(); -} - -function getResources() { - const resources: Record = {}; - for (const resource of getActiveResourcesInfo()) { - if (resources[resource] === undefined) { - resources[resource] = 0; - } - ++resources[resource]; - } - return resources; +// Trigger a mocha abort on interrupt. This doesn't stop the test runner +// immediately as it will still try to finish running the current in-progress +// suite before exiting, but the upside is that we are getting a way more robust +// cleanup where all the after hooks are taken into account as expected rarely +// leaving anythihg "hanging" +async function cleanupOnInterrupt() { + // First trigger an abort on the mocha runner + abortRunner?.(); + await runnerPromise; } -function cleanup() { - removeUserDataDir(); - - const disableStartStop = process.argv.includes('--disable-start-stop'); - const shouldTestCompassWeb = process.argv.includes('--test-compass-web'); - - if (!TEST_ATLAS_CLOUD_EXTERNAL_URL) { - if (!disableStartStop) { - if (shouldTestCompassWeb) { - debug('Stopping compass-web'); - try { - if (compassWeb.pid) { - debug(`killing compass-web [${compassWeb.pid}]`); - kill(compassWeb.pid, 'SIGINT'); - } else { - debug('no pid for compass-web'); - } - } catch (e) { - debug('Failed to stop compass-web', e); - } - } - - debug('Stopping MongoDB server'); - try { - crossSpawn.sync('npm', ['run', 'stop-servers'], { - // If it's taking too long we might as well kill the process and move on, - // mongodb-runner is flaky sometimes and in ci `posttest-ci` script will - // take care of additional clean up anyway - timeout: 120_000, - stdio: 'inherit', - }); - } catch (e) { - debug('Failed to stop MongoDB Server', e); - } - debug('Done stopping'); - } - } - - // Since the webdriverio update something is messing with the terminal's - // cursor. This brings it back. - crossSpawn.sync('tput', ['cnorm'], { stdio: 'inherit' }); - - // Log what's preventing the process from exiting normally to help debug the - // cases where the process hangs and gets killed 10 minutes later by evergreen - // because there's no output. - const intervalId = setInterval(() => { - console.log(getResources()); - }, 1_000); - - // Don't keep logging forever because then evergreen can't kill the job after - // 10 minutes of inactivity if we get into a broken state +function terminateOnTimeout() { + // Don't keep logging forever because then evergreen can't kill the job + // after 10 minutes of inactivity if we get into a broken state const timeoutId = setTimeout(() => { - clearInterval(intervalId); - - // Just exit now rather than waiting for 10 minutes just so evergreen can - // kill the task and fail anyway. - process.exit(process.exitCode ?? 1); - }, 60_000); - - // No need to hold things up for a minute if there's nothing else preventing - // the process from exiting. - intervalId.unref(); + debug('Mocha is still cleaning up:'); + // Log what's preventing the process from exiting normally to help debug the + // cases where the process hangs and gets killed 10 minutes later by evergreen + // because there's no output. + logRunning(console); + debug('Terminating the process ...'); + // Just exit now rather than waiting for 10 minutes just so evergreen + // can kill the task and fail anyway. + process.exitCode ??= 1; + process.exit(); + }, 30_000); timeoutId.unref(); } -async function main() { - await setup(); - - const shouldTestCompassWeb = process.argv.includes('--test-compass-web'); - - if (!shouldTestCompassWeb) { - if (!process.env.CHROME_VERSION) { - // written during setup() if disableStartStop is false - const versionsJSON = await fs.promises.readFile( - 'electron-versions.json', - 'utf8' - ); - const versions = JSON.parse(versionsJSON); - process.env.CHROME_VERSION = versions.chrome; - } - debug( - 'Chrome version corresponding to Electron:', - process.env.CHROME_VERSION - ); - } - - // These are mutually exclusive since compass-web is always going to browse to - // the running webserver. - const shouldTestPackagedApp = - process.argv.includes('--test-packaged-app') && !shouldTestCompassWeb; - - // Skip this step if you are running tests consecutively and don't need to - // rebuild modules all the time. Also no need to ever recompile when testing - // compass-web. - const noNativeModules = - process.argv.includes('--no-native-modules') || shouldTestCompassWeb; - - // Skip this step if you want to run tests against your own compilation (e.g, - // a dev build or a build running in watch mode that autorecompiles). Also no - // need to recompile when testing compass-web. - const noCompile = - process.argv.includes('--no-compile') || shouldTestCompassWeb; - - if (shouldTestPackagedApp) { - process.env.TEST_PACKAGED_APP = '1'; - debug('Building Compass before running the tests ...'); - await buildCompass(); - } else { - delete process.env.TEST_PACKAGED_APP; - - // set coverage to the root of the monorepo so it will be generated for - // everything and not just packages/compass - process.env.COVERAGE = path.dirname(path.dirname(COMPASS_PATH)); - - debug('Preparing Compass before running the tests'); - if (!noNativeModules) { - debug('Rebuilding native modules ...'); - await rebuildNativeModules(); - } - if (!noCompile) { - debug('Compiling Compass assets ...'); - await compileCompassAssets(); - } - } +let runnerPromise: Promise | undefined; +async function main() { const e2eTestGroupsAmount = parseInt(process.env.E2E_TEST_GROUPS || '1'); const e2eTestGroup = parseInt(process.env.E2E_TEST_GROUP || '1'); const e2eTestFilter = process.env.E2E_TEST_FILTER || '*'; @@ -269,126 +73,103 @@ async function main() { await glob(`tests/**/${e2eTestFilter}.{test,spec}.ts`, { cwd: __dirname, }) - ).filter((value, index, array) => { - const testsPerGroup = Math.ceil(array.length / e2eTestGroupsAmount); - const minGroupIndex = (e2eTestGroup - 1) * testsPerGroup; - const maxGroupIndex = minGroupIndex + testsPerGroup - 1; + ) + .filter((_value, index, array) => { + const testsPerGroup = Math.ceil(array.length / e2eTestGroupsAmount); + const minGroupIndex = (e2eTestGroup - 1) * testsPerGroup; + const maxGroupIndex = minGroupIndex + testsPerGroup - 1; - return index >= minGroupIndex && index <= maxGroupIndex; - }); + return index >= minGroupIndex && index <= maxGroupIndex; + }) + .sort((a, b) => { + // The only test file that's interested in the first-run experience (at the + // time of writing) is time-to-first-query.ts and that happens to be + // alphabetically right at the end. Which is fine, but the first test to run + // will also get the slow first run experience for no good reason unless it is + // the time-to-first-query.ts test. + // So yeah.. this is a bit of a micro optimisation. + if (a === FIRST_TEST) { + return -1; + } else if (b === FIRST_TEST) { + return 1; + } else { + return 0; + } + }); - console.info('Test files:', tests); + debug('Test files:', tests); - // The only test file that's interested in the first-run experience (at the - // time of writing) is time-to-first-query.ts and that happens to be - // alphabetically right at the end. Which is fine, but the first test to run - // will also get the slow first run experience for no good reason unless it is - // the time-to-first-query.ts test. - // So yeah.. this is a bit of a micro optimisation. - tests.sort((a, b) => { - if (a === FIRST_TEST) return -1; - else if (b === FIRST_TEST) return 1; - else return 0; + const mocha = new Mocha({ + timeout: MOCHA_DEFAULT_TIMEOUT, + bail: MOCHA_BAIL, + reporter: require.resolve('@mongodb-js/mocha-config-compass/reporter'), }); - // Ensure the insert-data mocha hooks are run. - tests.unshift(path.join('helpers', 'insert-data.ts')); + // @ts-expect-error mocha types are incorrect, global setup this is bound to + // runner, not context + mocha.globalSetup(mochaGlobalSetup); + mocha.enableGlobalSetup(true); - const bail = process.argv.includes('--bail'); + mocha.globalTeardown(mochaGlobalTeardown); + mocha.enableGlobalTeardown(true); - const mocha = new Mocha({ - timeout: 240_000, // kinda arbitrary, but longer than waitforTimeout set in helpers/compass.ts so the test can fail before it times out - bail, - reporter: path.resolve( - __dirname, - '..', - '..', - 'configs/mocha-config-compass/reporter.js' - ), - }); + mocha.rootHooks(mochaRootHooks); // print the test order for debugging purposes and so we can tweak the groups later - console.log('test order', tests); + debug('Test order:', tests); tests.forEach((testPath: string) => { mocha.addFile(path.join(__dirname, testPath)); }); - const metricsConnection = process.env.E2E_TESTS_METRICS_URI; - if (metricsConnection) { - debug('Connecting to E2E_TESTS_METRICS_URI'); - // only require it down here because it gets rebuilt up top - const mongodb = await import('mongodb'); - metricsClient = new mongodb.MongoClient(metricsConnection); - await metricsClient.connect(); - } else { - debug('Not logging metrics to a database.'); - } - debug('Running E2E tests'); - // mocha.run has a callback and returns a result, so just promisify it manually - const { resultLogger, failures } = await new Promise<{ - resultLogger: ResultLogger; - failures: number; - }>((resolve, reject) => { - // eslint-disable-next-line prefer-const - let resultLogger: ResultLogger; - - const runner = mocha.run((failures: number) => { + runnerPromise = new Promise((resolve) => { + mocha.run((failures: number) => { + debug('Finished running e2e tests', { failures }); process.exitCode = failures ? 1 : 0; - resolve({ resultLogger, failures }); - }); - - debug('Initialising ResultLogger'); - resultLogger = new ResultLogger(metricsClient, runner); - - // Synchronously create the ResultLogger so it can start listening to events - // on runner immediately after calling mocha.run() before any of the events - // fire. - resultLogger.init().catch((err: Error) => { - // reject() doesn't stop mocha.run()... - reject(err); + // Since the webdriverio update something is messing with the terminal's + // cursor. This brings it back. + crossSpawn.sync('tput', ['cnorm'], { stdio: 'inherit' }); + terminateOnTimeout(); + resolve(failures); }); }); - - await resultLogger.done(failures); } process.once('SIGINT', () => { - debug(`Process was interrupted. Cleaning-up and exiting.`); - cleanup(); - process.kill(process.pid, 'SIGINT'); + debug(`Process was interrupted. Waiting for mocha to abort and clean-up ...`); + void (async () => { + await cleanupOnInterrupt(); + process.kill(process.pid, 'SIGINT'); + })(); }); process.once('SIGTERM', () => { - debug(`Process was terminated. Cleaning-up and exiting.`); - cleanup(); - process.kill(process.pid, 'SIGTERM'); + debug(`Process was terminated. Waiting for mocha to abort and clean-up ...`); + void (async () => { + await cleanupOnInterrupt(); + process.kill(process.pid, 'SIGTERM'); + })(); }); process.once('uncaughtException', (err: Error) => { - debug('Uncaught exception. Cleaning-up and exiting.'); - cleanup(); - throw err; + debug('Uncaught exception:'); + console.error(err.stack || err.message || err); + debug('Waiting for mocha to abort and clean-up ...'); + process.exitCode = 1; + void cleanupOnInterrupt(); }); process.on('unhandledRejection', (err: Error) => { - debug('Unhandled exception. Cleaning-up and exiting.'); - cleanup(); + debug('Unhandled exception:'); console.error(err.stack || err.message || err); + debug('Waiting for mocha to abort and clean-up ...'); process.exitCode = 1; + void cleanupOnInterrupt(); }); async function run() { - try { - await main(); - } finally { - if (metricsClient) { - await metricsClient.close(); - } - - cleanup(); - } + await main(); } void run(); diff --git a/packages/compass-e2e-tests/package.json b/packages/compass-e2e-tests/package.json index 64d88aeb47b..3e4e32492f4 100644 --- a/packages/compass-e2e-tests/package.json +++ b/packages/compass-e2e-tests/package.json @@ -10,7 +10,6 @@ "lint": "npm run eslint . && npm run prettier -- --check .", "depcheck": "depcheck", "check": "npm run lint && npm run depcheck", - "pretest": "npm run unzip-fixtures", "test": "xvfb-maybe --auto-servernum --server-args=\"-screen 0 1432x840x24\" -- ts-node index.ts", "test-ci": "npm run test", "posttest-ci": "npm run coverage-report", @@ -23,14 +22,12 @@ "stop-server-2": "mongodb-runner stop --id=e2e-2", "start-servers": "npm run start-server-1 && npm run start-server-2", "stop-servers": "npm run stop-server-1 && npm run stop-server-2", - "unzip-fixtures": "ts-node ./scripts/gunzip.ts fixtures/*.gz", "test-noserver": "env DEBUG=hadron*,mongo*,compass*,xvfb-maybe* npm run test -- --disable-start-stop --bail", "test-noserver-nocompile": "env DEBUG=hadron*,mongo*,compass*,xvfb-maybe* npm run test -- --no-native-modules --no-compile --disable-start-stop --bail", "test-web": "env DEBUG=hadron*,mongo*,compass*,xvfb-maybe* npm run test -- --test-compass-web", "test-web-noserver": "env DEBUG=hadron*,mongo*,compass*,xvfb-maybe* npm run test -- --test-compass-web --disable-start-stop --bail", "coverage-merge": "nyc merge .log/coverage .nyc_output/coverage.json", - "coverage-report": "npm run coverage-merge && nyc report", - "server-info": "ts-node ./scripts/server-info.ts" + "coverage-report": "npm run coverage-merge && nyc report" }, "devDependencies": { "@electron/rebuild": "^3.6.2", @@ -53,13 +50,14 @@ "debug": "^4.3.4", "depcheck": "^1.4.1", "electron": "^30.5.1", + "electron-to-chromium": "^1.5.41", "eslint": "^7.25.0", - "fast-glob": "^3.2.7", "glob": "^10.2.5", "hadron-build": "^25.5.12", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb": "^6.9.0", + "mongodb-build-info": "^1.7.2", "mongodb-connection-string-url": "^3.0.1", "mongodb-log-writer": "^1.4.2", "mongodb-runner": "^5.6.3", @@ -73,6 +71,7 @@ "tree-kill": "^1.2.2", "ts-node": "^10.9.1", "webdriverio": "^8.40.0", + "why-is-node-running": "^2.3.0", "xvfb-maybe": "^0.2.1" } } diff --git a/packages/compass-e2e-tests/scripts/gunzip.ts b/packages/compass-e2e-tests/scripts/gunzip.ts deleted file mode 100755 index 8974f4588a1..00000000000 --- a/packages/compass-e2e-tests/scripts/gunzip.ts +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env ts-node - -import fastGlob from 'fast-glob'; -import { createReadStream, createWriteStream } from 'fs'; -import { pipeline } from 'stream'; -import { promisify } from 'util'; -import { createGunzip } from 'zlib'; - -const pipe = promisify(pipeline); - -async function gunzip(input: string, output: string) { - const readStream = createReadStream(input); - const gunzip = createGunzip(); - const writeStream = createWriteStream(output); - - await pipe(readStream, gunzip, writeStream); -} - -async function run() { - // windows does not expand * automatically - const filenames = await fastGlob(process.argv.slice(2)); - - for (const input of filenames) { - const output = input.replace(/\.gz$/, ''); - console.log(input, '=>', output); - await gunzip(input, output); - } -} - -if (require.main === module) { - run().catch((err: Error) => { - console.error('An error occurred:', err); - process.exitCode = 1; - }); -} - -module.exports = gunzip; diff --git a/packages/compass-e2e-tests/scripts/server-info.ts b/packages/compass-e2e-tests/scripts/server-info.ts deleted file mode 100644 index abe9bf1f883..00000000000 --- a/packages/compass-e2e-tests/scripts/server-info.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { MongoClient } from 'mongodb'; - -export async function getServerVersion(connectionString: string) { - const client = await MongoClient.connect(connectionString); - try { - const buildInfo = await client.db('admin').command({ buildInfo: 1 }); - return { - version: buildInfo.version, - enterprise: buildInfo.modules?.includes('enterprise') || false, - }; - } finally { - await client.close(); - } -} - -void (async () => { - try { - const index = process.argv.indexOf('--connectionString') ?? -1; - const connectionString = - index === -1 ? 'mongodb://127.0.0.1:27091' : process.argv[index + 1]; - console.log(JSON.stringify(await getServerVersion(connectionString))); - } catch (err) { - const { name, message } = err as Error; - console.error(`${name}: ${message}`); - process.exitCode = 1; - } -})(); diff --git a/packages/compass-e2e-tests/scripts/write-electron-versions.sh b/packages/compass-e2e-tests/scripts/write-electron-versions.sh deleted file mode 100755 index afaf990782a..00000000000 --- a/packages/compass-e2e-tests/scripts/write-electron-versions.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash - -# To get the versions of various electron dependencies, including chromium we're -# mostly interested in, we run a script file using currently installed electron -# version binary. We don't use interactive electron repl (electron -i) here -# instead because Windows doesn't support it. -script=" -const fs = require('fs'); -fs.writeFileSync( - 'electron-versions.json', - JSON.stringify(process.versions), - 'utf8' -); -process.exit(); -" -script_name=write-electron-versions.js -echo $script >$script_name -npx electron --no-sandbox $script_name diff --git a/packages/compass-web/package.json b/packages/compass-web/package.json index 9d5f81f1310..28ca28cf751 100644 --- a/packages/compass-web/package.json +++ b/packages/compass-web/package.json @@ -40,6 +40,7 @@ "webpack": "webpack-compass", "postcompile": "npm run typescript", "typescript": "tsc -p tsconfig-build.json --emitDeclarationOnly", + "prestart": "npm run compile -w @mongodb-js/webpack-config-compass", "start": "electron ./scripts/electron-proxy.js", "analyze": "npm run webpack -- --mode production --analyze", "watch": "npm run webpack -- --mode development --watch", diff --git a/packages/compass/package.json b/packages/compass/package.json index 4b35721ffd2..3a1bc9753ff 100644 --- a/packages/compass/package.json +++ b/packages/compass/package.json @@ -141,7 +141,7 @@ "scripts": { "install": "node scripts/download-fonts.js && node scripts/download-csfle.js", "electron-rebuild": "electron-rebuild --only kerberos,keytar,interruptor,os-dns-native,win-export-certificate-and-key,macos-export-certificate-and-key --prebuild-tag-prefix not-real-prefix-to-force-rebuild", - "prestart": "npm run electron-rebuild", + "prestart": "npm run electron-rebuild && npm run compile --workspace=@mongodb-js/webpack-config-compass", "start": "HADRON_DISTRIBUTION=${HADRON_DISTRIBUTION:-compass} npm run webpack serve -- --mode development", "test-electron": "npm run test-main && npm run test-renderer", "test-main": "xvfb-maybe electron-mocha --no-sandbox \"./src/main/**/*.spec.*\" \"./src/main/**/*.test.*\"", From 8884e7fae93c161e929f21a58f10b7ce8cbca354 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 23 Oct 2024 11:59:57 +0000 Subject: [PATCH 6/6] chore: update AUTHORS, THIRD-PARTY-NOTICES, Security Test Summary (#6392) Update report Co-authored-by: gribnoysup <5036933+gribnoysup@users.noreply.github.com> --- docs/tracking-plan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tracking-plan.md b/docs/tracking-plan.md index 4f6b27fd4a0..88308e14087 100644 --- a/docs/tracking-plan.md +++ b/docs/tracking-plan.md @@ -1,7 +1,7 @@ # Compass Tracking Plan -Generated on Wed, Oct 23, 2024 at 11:42 AM +Generated on Wed, Oct 23, 2024 at 11:59 AM ## Table of Contents