From 4015fd90c6a2a889a6eff66d03bcc6a39b4500d3 Mon Sep 17 00:00:00 2001 From: tomas Date: Wed, 24 Jun 2026 06:45:55 +0000 Subject: [PATCH 1/7] test(e2e): add ExTester end-to-end test for the Deepnote notebook flow Adds a black-box ExTester (vscode-extension-tester) E2E suite that drives the full Deepnote happy path through the real VS Code UI: open a workspace folder and a one-notebook `.deepnote` file, create a Deepnote environment, select it for the notebook (kernel connects, venv + deepnote-toolkit provisioned), run the cell, and assert the rendered output contains "hello world". Beyond the test itself, this wires up reproducible setup and the fixes required to make it pass in a headless/sandboxed VS Code instance: - enable-proposed-api.js allow-lists the extension's proposed APIs in the test VS Code's product.json (the extension does not activate otherwise) - open the containing folder as a workspace (the serializer otherwise blocks on a "no workspace folder" snapshot warning) and open the notebook via Quick Open, since ExTester's `code -r` reuse-window silently no-ops in the sandbox - accept the simple folder dialog via its OK button (Enter navigates into dirs) - run via the toolbar "Run All" button and re-issue until output renders (deepnote.runallcells is gated behind context keys unset under automation) - idempotent environment creation with a stable name (reuses the venv) - exclude e2e artifacts from the VSIX in .vscodeignore Verified passing locally on headless Linux (Xvfb, VS Code 1.111.0), including a clean-state run that provisions the venv from scratch. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01LQk7n13UfmeDo2ujy8X4LX --- .gitignore | 4 + .vscodeignore | 2 + package-lock.json | 5922 +++++++++++++++++++++++- package.json | 10 +- specs/e2e-extester-testing-plan.md | 1250 +++++ test/e2e/.mocharc.js | 9 + test/e2e/enable-proposed-api.js | 81 + test/e2e/fixtures/hello-world.deepnote | 21 + test/e2e/settings.json | 13 + test/e2e/suite/helloWorld.e2e.test.ts | 439 ++ test/e2e/tsconfig.json | 21 + 11 files changed, 7754 insertions(+), 18 deletions(-) create mode 100644 specs/e2e-extester-testing-plan.md create mode 100644 test/e2e/.mocharc.js create mode 100644 test/e2e/enable-proposed-api.js create mode 100644 test/e2e/fixtures/hello-world.deepnote create mode 100644 test/e2e/settings.json create mode 100644 test/e2e/suite/helloWorld.e2e.test.ts create mode 100644 test/e2e/tsconfig.json diff --git a/.gitignore b/.gitignore index c573d86eb3..adbc881280 100644 --- a/.gitignore +++ b/.gitignore @@ -74,3 +74,7 @@ vscode.d.ts vscode.proposed.*.d.ts xunit-test-results.xml tsconfig.tsbuildinfo + +# ExTester (vscode-extension-tester) E2E artifacts +test-resources +.test-extensions diff --git a/.vscodeignore b/.vscodeignore index d85c365c4c..3817954b44 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -84,6 +84,8 @@ node_modules/** obj/** logs/** out/** +.test-extensions/** +test-resources/** precommit.hook pythonFiles/.env pythonFiles/**/*.pyc diff --git a/package-lock.json b/package-lock.json index b26d986762..8ce5859a48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -239,7 +239,8 @@ "typescript": "^5.8.3", "unicode-properties": "^1.3.1", "utf-8-validate": "^5.0.8", - "util": "^0.12.4" + "util": "^0.12.4", + "vscode-extension-tester": "^8.23.0" }, "engines": { "vscode": "^1.95.0" @@ -366,6 +367,23 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/@azu/format-text": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@azu/format-text/-/format-text-1.0.2.tgz", + "integrity": "sha512-Swi4N7Edy1Eqq82GxgEECXSSLyn6GOb5htRFPzBDdUkECGXtlf12ynO5oJSpWKPwCaUssOu7NfhDcCWpIC6Ywg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@azu/style-format": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@azu/style-format/-/style-format-1.0.1.tgz", + "integrity": "sha512-AHcTojlNBdD/3/KxIKlg8sxIWHfOtQszLvOpagLTO+bjC3u7SAszu1lf//u7JJC50aUSH+BVWDD/KvaA6Gfn5g==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "@azu/format-text": "^1.0.1" + } + }, "node_modules/@azure/abort-controller": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", @@ -392,6 +410,25 @@ "node": ">=20.0.0" } }, + "node_modules/@azure/core-client": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.2.tgz", + "integrity": "sha512-1D2LpsU7y9xrqKjdIbsB7PlrRePw0xsVV8p+AKTlzITrWmscajryfJCdDJB/oGwvDI5HmRo04eMMADB67uwAwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-rest-pipeline": "^1.22.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@azure/core-rest-pipeline": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.22.1.tgz", @@ -436,6 +473,107 @@ "node": ">=20.0.0" } }, + "node_modules/@azure/identity": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.13.1.tgz", + "integrity": "sha512-5C/2WD5Vb1lHnZS16dNQRPMjN6oV/Upba+C9nBIs15PmOi6A3ZGs4Lr2u60zw4S04gi+u3cEXiqTVP7M4Pz3kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.9.0", + "@azure/core-client": "^1.9.2", + "@azure/core-rest-pipeline": "^1.17.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.11.0", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^5.5.0", + "@azure/msal-node": "^5.1.0", + "open": "^10.1.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/identity/node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@azure/identity/node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@azure/identity/node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@azure/identity/node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@azure/identity/node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@azure/logger": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.3.0.tgz", @@ -449,6 +587,43 @@ "node": ">=20.0.0" } }, + "node_modules/@azure/msal-browser": { + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.14.0.tgz", + "integrity": "sha512-Dfl7hPZe9/JJwRhFFXHq2z1oHYBuGubmff3kWXOsd1AGgyXlqjNYAWuN/1JL/ZrcZBs8TKMjGSil6Rcc7E8VPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/msal-common": "16.9.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-common": { + "version": "16.9.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.9.0.tgz", + "integrity": "sha512-1MWGjqgUCRAYgLmVFZKp7fs3Rg1TFvIMgywY8ze2olNVvLlJoRThuoziWSDJuwwyJI5L4rnLb9Tyt5D9GvSLPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-node": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-5.2.5.tgz", + "integrity": "sha512-RUuewWk9JvWJS5Yiy8/74Lm1rQAWlrU/qg/Bgtk1jIauVRtnb9XKwS5Xg0J+Whwjesq9EVrBIFgQEP8vHxgezA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/msal-common": "16.9.0", + "jsonwebtoken": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/@babel/code-frame": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", @@ -942,6 +1117,13 @@ "node": ">=6.9.0" } }, + "node_modules/@bazel/runfiles": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@bazel/runfiles/-/runfiles-6.5.0.tgz", + "integrity": "sha512-RzahvqTkfpY2jsDxo8YItPX+/iZ6hbiikw1YhE0bA9EKBR5Og8Pa6FHn9PO9M0zaXRVsr0GFQLKbB/0rzy9SzA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", @@ -5536,6 +5718,13 @@ "loose-envify": "^1.1.0" } }, + "node_modules/@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "dev": true, + "license": "MIT" + }, "node_modules/@koa/cors": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@koa/cors/-/cors-5.0.0.tgz", @@ -6639,6 +6828,86 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@redhat-developer/locators": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/@redhat-developer/locators/-/locators-1.20.0.tgz", + "integrity": "sha512-SvS9lABIHDpTU0w7jTN3qsMxDSceAbbc3j0xba9Cd8xrDEMWpysnvt3WY4vtYalikMhXtkcSAf3L3adYTS7iqg==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "@redhat-developer/page-objects": ">=1.0.0", + "selenium-webdriver": ">=4.6.1" + } + }, + "node_modules/@redhat-developer/page-objects": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/@redhat-developer/page-objects/-/page-objects-1.20.0.tgz", + "integrity": "sha512-akY6gXRbUmlf2Cayp1A/TwoT0/eyK+/LReSBo7Nmfotk7QfR262k7ZilAmL3Vf/jmX3Gg0D8UrDZRFk8RTxS2Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "clipboardy": "^5.3.1", + "clone-deep": "^4.0.1", + "compare-versions": "^6.1.1", + "fs-extra": "^11.3.4", + "type-fest": "^4.41.0" + }, + "peerDependencies": { + "selenium-webdriver": ">=4.6.1", + "typescript": ">=4.6.2" + } + }, + "node_modules/@redhat-developer/page-objects/node_modules/fs-extra": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz", + "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@redhat-developer/page-objects/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@redhat-developer/page-objects/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@redhat-developer/page-objects/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/@rjsf/utils": { "version": "5.24.13", "resolved": "https://registry.npmjs.org/@rjsf/utils/-/utils-5.24.13.tgz", @@ -6658,12 +6927,349 @@ "react": "^16.14.0 || >=17" } }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@secretlint/config-creator": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/config-creator/-/config-creator-10.2.2.tgz", + "integrity": "sha512-BynOBe7Hn3LJjb3CqCHZjeNB09s/vgf0baBaHVw67w7gHF0d25c3ZsZ5+vv8TgwSchRdUCRrbbcq5i2B1fJ2QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.2.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/config-loader": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/config-loader/-/config-loader-10.2.2.tgz", + "integrity": "sha512-ndjjQNgLg4DIcMJp4iaRD6xb9ijWQZVbd9694Ol2IszBIbGPPkwZHzJYKICbTBmh6AH/pLr0CiCaWdGJU7RbpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/profiler": "^10.2.2", + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", + "ajv": "^8.17.1", + "debug": "^4.4.1", + "rc-config-loader": "^4.1.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/config-loader/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@secretlint/config-loader/node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@secretlint/config-loader/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@secretlint/core": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/core/-/core-10.2.2.tgz", + "integrity": "sha512-6rdwBwLP9+TO3rRjMVW1tX+lQeo5gBbxl1I5F8nh8bgGtKwdlCMhMKsBWzWg1ostxx/tIG7OjZI0/BxsP8bUgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/profiler": "^10.2.2", + "@secretlint/types": "^10.2.2", + "debug": "^4.4.1", + "structured-source": "^4.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/formatter": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/formatter/-/formatter-10.2.2.tgz", + "integrity": "sha512-10f/eKV+8YdGKNQmoDUD1QnYL7TzhI2kzyx95vsJKbEa8akzLAR5ZrWIZ3LbcMmBLzxlSQMMccRmi05yDQ5YDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", + "@textlint/linter-formatter": "^15.2.0", + "@textlint/module-interop": "^15.2.0", + "@textlint/types": "^15.2.0", + "chalk": "^5.4.1", + "debug": "^4.4.1", + "pluralize": "^8.0.0", + "strip-ansi": "^7.1.0", + "table": "^6.9.0", + "terminal-link": "^4.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/formatter/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@secretlint/formatter/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@secretlint/formatter/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@secretlint/formatter/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@secretlint/formatter/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@secretlint/formatter/node_modules/supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + } + }, + "node_modules/@secretlint/formatter/node_modules/terminal-link": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-4.0.0.tgz", + "integrity": "sha512-lk+vH+MccxNqgVqSnkMVKx4VLJfnLjDBGzH16JVZjKE2DoxP57s6/vt6JmXV5I3jBcfGrxNrYtC+mPtU7WJztA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "supports-hyperlinks": "^3.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@secretlint/node": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/node/-/node-10.2.2.tgz", + "integrity": "sha512-eZGJQgcg/3WRBwX1bRnss7RmHHK/YlP/l7zOQsrjexYt6l+JJa5YhUmHbuGXS94yW0++3YkEJp0kQGYhiw1DMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/config-loader": "^10.2.2", + "@secretlint/core": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/profiler": "^10.2.2", + "@secretlint/source-creator": "^10.2.2", + "@secretlint/types": "^10.2.2", + "debug": "^4.4.1", + "p-map": "^7.0.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/node/node_modules/p-map": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@secretlint/profiler": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/profiler/-/profiler-10.2.2.tgz", + "integrity": "sha512-qm9rWfkh/o8OvzMIfY8a5bCmgIniSpltbVlUVl983zDG1bUuQNd1/5lUEeWx5o/WJ99bXxS7yNI4/KIXfHexig==", + "dev": true, + "license": "MIT" + }, + "node_modules/@secretlint/resolver": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/resolver/-/resolver-10.2.2.tgz", + "integrity": "sha512-3md0cp12e+Ae5V+crPQYGd6aaO7ahw95s28OlULGyclyyUtf861UoRGS2prnUrKh7MZb23kdDOyGCYb9br5e4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@secretlint/secretlint-formatter-sarif": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-formatter-sarif/-/secretlint-formatter-sarif-10.2.2.tgz", + "integrity": "sha512-ojiF9TGRKJJw308DnYBucHxkpNovDNu1XvPh7IfUp0A12gzTtxuWDqdpuVezL7/IP8Ua7mp5/VkDMN9OLp1doQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-sarif-builder": "^3.2.0" + } + }, + "node_modules/@secretlint/secretlint-rule-no-dotenv": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-no-dotenv/-/secretlint-rule-no-dotenv-10.2.2.tgz", + "integrity": "sha512-KJRbIShA9DVc5Va3yArtJ6QDzGjg3PRa1uYp9As4RsyKtKSSZjI64jVca57FZ8gbuk4em0/0Jq+uy6485wxIdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.2.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/secretlint-rule-preset-recommend": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-preset-recommend/-/secretlint-rule-preset-recommend-10.2.2.tgz", + "integrity": "sha512-K3jPqjva8bQndDKJqctnGfwuAxU2n9XNCPtbXVI5JvC7FnQiNg/yWlQPbMUlBXtBoBGFYp08A94m6fvtc9v+zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/source-creator": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/source-creator/-/source-creator-10.2.2.tgz", + "integrity": "sha512-h6I87xJfwfUTgQ7irWq7UTdq/Bm1RuQ/fYhA3dtTIAop5BwSFmZyrchph4WcoEvbN460BWKmk4RYSvPElIIvxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.2.2", + "istextorbinary": "^9.5.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/types": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/types/-/types-10.2.2.tgz", + "integrity": "sha512-Nqc90v4lWCXyakD6xNyNACBJNJ0tNCwj2WNk/7ivyacYHxiITVgmLUFXTBOeCdy79iz6HtN9Y31uw/jbLrdOAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "license": "MIT" }, + "node_modules/@sindresorhus/is": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz", + "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -7052,6 +7658,143 @@ "tailwindcss": "4.1.14" } }, + "node_modules/@textlint/ast-node-types": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-15.7.1.tgz", + "integrity": "sha512-Wii5UgUKFEh9Uv6wbq1zr4/Kf+dtjiUuzPrrXzKp8H+ifkvKNzi23V4Nz+6wVyHQn5T28AFuc8VH8OtzvGYecA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/linter-formatter": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/linter-formatter/-/linter-formatter-15.7.1.tgz", + "integrity": "sha512-TdwZ/debWYFD05K3CcoHtwvnCrza29wZxD+BjDTk/V5N7iRqkK1dTTHSD4A8AIgROLiDkHJmIKQbasbmsg8AvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azu/format-text": "^1.0.2", + "@azu/style-format": "^1.0.1", + "@textlint/module-interop": "15.7.1", + "@textlint/resolver": "15.7.1", + "@textlint/types": "15.7.1", + "chalk": "^4.1.2", + "debug": "^4.4.3", + "js-yaml": "^4.1.1", + "lodash": "^4.18.1", + "pluralize": "^2.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "table": "^6.9.0", + "text-table": "^0.2.0" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/linter-formatter/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/pluralize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-2.0.0.tgz", + "integrity": "sha512-TqNZzQCD4S42De9IfnnBvILN7HAW7riLqsCyp8lgjXeysyPlX5HhqKAcJHHHb9XskE4/a+7VGC9zzx8Ls0jOAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/linter-formatter/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@textlint/module-interop": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/module-interop/-/module-interop-15.7.1.tgz", + "integrity": "sha512-Jg+sQW2L/cRJypk59wtcMUVVpt8vmit5ZMT3gUnFwevP3A6Qp1HfOtUy9ObT4hBX3lOSGT/ekcCDxR1pL7uH1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/resolver": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/resolver/-/resolver-15.7.1.tgz", + "integrity": "sha512-8XnO0pgF6mXnm41VvWmBbEIdGPhiCUt31uLZkOis1ECeg/1SoUcIT6Mx/F0e1rukq8l0UlOSeY9a31CsvRMK0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/types": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/types/-/types-15.7.1.tgz", + "integrity": "sha512-Vye/GmFNBTgVzZFtIFJTmLB+s2A7oIADxNG6r9UhfPuY+Czv0z5G3xeyFZZudPlfxURsKUyPIU5XsjOFqVp33A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@textlint/ast-node-types": "15.7.1" + } + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -7540,6 +8283,13 @@ "hoist-non-react-statics": "^3.3.0" } }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/http-errors": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", @@ -7832,11 +8582,29 @@ "redux": "^4.0.0" } }, + "node_modules/@types/sarif": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@types/sarif/-/sarif-2.1.7.tgz", + "integrity": "sha512-kRz0VEkJqWLf1LLVN4pT1cg1Z9wAuvI6L97V3m2f5B76Tg8d413ddvLBPTEHAZJlnn4XSvu0FkZtViCQGVyrXQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/scheduler": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, + "node_modules/@types/selenium-webdriver": { + "version": "4.35.6", + "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-4.35.6.tgz", + "integrity": "sha512-8nfyMRi4VvkY9QrQGyY/zkleAhnjnmE8YtdEeoCrWe3izp1P9vo9f5VTNRYF0up+l+kn+VuZah+je+bLddNV+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/ws": "*" + } + }, "node_modules/@types/semver": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.5.0.tgz", @@ -8814,6 +9582,407 @@ "node": ">= 14" } }, + "node_modules/@vscode/vsce": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.9.2.tgz", + "integrity": "sha512-XSxMosEEDO6vLxELAHVkwmhC0qe0ijZni2jB9Rcs8kQsW4lhTDQ/wMzmwFs/buotAWSnpmUp/dRWD2ufG3UYKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/identity": "^4.1.0", + "@secretlint/node": "^10.1.2", + "@secretlint/secretlint-formatter-sarif": "^10.1.2", + "@secretlint/secretlint-rule-no-dotenv": "^10.1.2", + "@secretlint/secretlint-rule-preset-recommend": "^10.1.2", + "@vscode/vsce-sign": "^2.0.0", + "azure-devops-node-api": "^12.5.0", + "chalk": "^4.1.2", + "cheerio": "^1.0.0-rc.9", + "cockatiel": "^3.1.2", + "commander": "^12.1.0", + "form-data": "^4.0.0", + "glob": "^13.0.6", + "hosted-git-info": "^4.0.2", + "jsonc-parser": "^3.2.0", + "leven": "^3.1.0", + "markdown-it": "^14.1.0", + "mime": "^1.3.4", + "minimatch": "^10.2.2", + "parse-semver": "^1.1.1", + "read": "^1.0.7", + "secretlint": "^10.1.2", + "semver": "^7.5.2", + "tmp": "^0.2.3", + "typed-rest-client": "^1.8.4", + "url-join": "^4.0.1", + "xml2js": "^0.5.0", + "yauzl": "^3.2.1", + "yazl": "^2.2.2" + }, + "bin": { + "vsce": "vsce" + }, + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "keytar": "^7.7.0" + } + }, + "node_modules/@vscode/vsce-sign": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign/-/vsce-sign-2.0.9.tgz", + "integrity": "sha512-8IvaRvtFyzUnGGl3f5+1Cnor3LqaUWvhaUjAYO8Y39OUYlOf3cRd+dowuQYLpZcP3uwSG+mURwjEBOSq4SOJ0g==", + "dev": true, + "hasInstallScript": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optionalDependencies": { + "@vscode/vsce-sign-alpine-arm64": "2.0.6", + "@vscode/vsce-sign-alpine-x64": "2.0.6", + "@vscode/vsce-sign-darwin-arm64": "2.0.6", + "@vscode/vsce-sign-darwin-x64": "2.0.6", + "@vscode/vsce-sign-linux-arm": "2.0.6", + "@vscode/vsce-sign-linux-arm64": "2.0.6", + "@vscode/vsce-sign-linux-x64": "2.0.6", + "@vscode/vsce-sign-win32-arm64": "2.0.6", + "@vscode/vsce-sign-win32-x64": "2.0.6" + } + }, + "node_modules/@vscode/vsce-sign-alpine-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-arm64/-/vsce-sign-alpine-arm64-2.0.6.tgz", + "integrity": "sha512-wKkJBsvKF+f0GfsUuGT0tSW0kZL87QggEiqNqK6/8hvqsXvpx8OsTEc3mnE1kejkh5r+qUyQ7PtF8jZYN0mo8Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "alpine" + ] + }, + "node_modules/@vscode/vsce-sign-alpine-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-x64/-/vsce-sign-alpine-x64-2.0.6.tgz", + "integrity": "sha512-YoAGlmdK39vKi9jA18i4ufBbd95OqGJxRvF3n6ZbCyziwy3O+JgOpIUPxv5tjeO6gQfx29qBivQ8ZZTUF2Ba0w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "alpine" + ] + }, + "node_modules/@vscode/vsce-sign-darwin-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-arm64/-/vsce-sign-darwin-arm64-2.0.6.tgz", + "integrity": "sha512-5HMHaJRIQuozm/XQIiJiA0W9uhdblwwl2ZNDSSAeXGO9YhB9MH5C4KIHOmvyjUnKy4UCuiP43VKpIxW1VWP4tQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@vscode/vsce-sign-darwin-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-x64/-/vsce-sign-darwin-x64-2.0.6.tgz", + "integrity": "sha512-25GsUbTAiNfHSuRItoQafXOIpxlYj+IXb4/qarrXu7kmbH94jlm5sdWSCKrrREs8+GsXF1b+l3OB7VJy5jsykw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@vscode/vsce-sign-linux-arm": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm/-/vsce-sign-linux-arm-2.0.6.tgz", + "integrity": "sha512-UndEc2Xlq4HsuMPnwu7420uqceXjs4yb5W8E2/UkaHBB9OWCwMd3/bRe/1eLe3D8kPpxzcaeTyXiK3RdzS/1CA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-linux-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm64/-/vsce-sign-linux-arm64-2.0.6.tgz", + "integrity": "sha512-cfb1qK7lygtMa4NUl2582nP7aliLYuDEVpAbXJMkDq1qE+olIw/es+C8j1LJwvcRq1I2yWGtSn3EkDp9Dq5FdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-linux-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-x64/-/vsce-sign-linux-x64-2.0.6.tgz", + "integrity": "sha512-/olerl1A4sOqdP+hjvJ1sbQjKN07Y3DVnxO4gnbn/ahtQvFrdhUi0G1VsZXDNjfqmXw57DmPi5ASnj/8PGZhAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-win32-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-arm64/-/vsce-sign-win32-arm64-2.0.6.tgz", + "integrity": "sha512-ivM/MiGIY0PJNZBoGtlRBM/xDpwbdlCWomUWuLmIxbi1Cxe/1nooYrEQoaHD8ojVRgzdQEUzMsRbyF5cJJgYOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vscode/vsce-sign-win32-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-x64/-/vsce-sign-win32-x64-2.0.6.tgz", + "integrity": "sha512-mgth9Kvze+u8CruYMmhHw6Zgy3GRX2S+Ed5oSokDEK5vPEwGGKnmuXua9tmFhomeAnhgJnL4DCna3TiNuGrBTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vscode/vsce/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@vscode/vsce/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@vscode/vsce/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@vscode/vsce/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@vscode/vsce/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@vscode/vsce/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vscode/vsce/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@vscode/vsce/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vscode/vsce/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@vscode/vsce/node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@vscode/vsce/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@vscode/vsce/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vscode/vsce/node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vscode/vsce/node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@vscode/vsce/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@vscode/vsce/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@vscode/zeromq": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/@vscode/zeromq/-/zeromq-0.2.7.tgz", @@ -9711,6 +10880,17 @@ "dequal": "^2.0.3" } }, + "node_modules/azure-devops-node-api": { + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-12.5.0.tgz", + "integrity": "sha512-R5eFskGvOm3U/GzeAuxRkUsAl0hrAwGgWn6zAd2KrZmrEhWZVqLew4OOupbQlXUuojUzpGtq62SmdhJ06N88og==", + "dev": true, + "license": "MIT", + "dependencies": { + "tunnel": "0.0.6", + "typed-rest-client": "^1.8.4" + } + }, "node_modules/b4a": { "version": "1.6.6", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", @@ -10197,6 +11377,22 @@ "node": ">=8" } }, + "node_modules/binaryextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-6.11.0.tgz", + "integrity": "sha512-sXnYK/Ij80TO3lcqZVV2YgfKN5QjUWIRk/XSm2J/4bd/lPko3lvk0O4ZppH6m+6hB2/GTu+ptNwVFe1xh+QLQw==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, "node_modules/bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -10217,6 +11413,13 @@ "readable-stream": "^3.4.0" } }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, "node_modules/bn.js": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.3.tgz", @@ -10308,6 +11511,13 @@ "resolved": "https://registry.npmjs.org/bootstrap-less/-/bootstrap-less-3.3.8.tgz", "integrity": "sha1-cfKd1af//t/onxYFu63+CjONrlM=" }, + "node_modules/boundary": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/boundary/-/boundary-2.0.0.tgz", + "integrity": "sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/bplist-parser": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", @@ -10573,6 +11783,34 @@ "ieee754": "^1.2.1" } }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "dev": true, + "license": "MIT" + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/buffer-equal": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", @@ -10587,6 +11825,13 @@ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "license": "BSD-3-Clause" }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -10653,6 +11898,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/byte-counter": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/byte-counter/-/byte-counter-0.1.0.tgz", + "integrity": "sha512-jheRLVMeUKrDBjVw2O5+k4EvR4t9wtxHL+bo/LxfkxsVeuGMy3a5SEGgXdAFA4FSzTrU8rQXQIrsZ3oBq5a0pQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -10849,6 +12107,88 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cacheable-request": { + "version": "13.0.19", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-13.0.19.tgz", + "integrity": "sha512-SVXGH037+Mo1aIMO5B2UcleR43FGjFdN+M8JObSyEoQ2Mn4CODRWx28gN5jiTF0n5ItsgtIZfyargMNs8GX4kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "^4.2.0", + "get-stream": "^9.0.1", + "http-cache-semantics": "^4.2.0", + "keyv": "^5.6.0", + "mimic-response": "^4.0.0", + "normalize-url": "^8.1.1", + "responselike": "^4.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cacheable-request/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cacheable-request/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, + "node_modules/cacheable-request/node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/caching-transform": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", @@ -11131,6 +12471,129 @@ "node": "*" } }, + "node_modules/cheerio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", + "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.1.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.19.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cheerio/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/cheerio/node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/cheerio/node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/cheerio/node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/cheerio/node_modules/undici": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/cheerio/node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -11369,6 +12832,198 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/clipboard-image": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/clipboard-image/-/clipboard-image-0.1.0.tgz", + "integrity": "sha512-SWk7FgaXLNFld19peQ/rTe0n97lwR1WbkqxV6JKCAOh7U52AKV/PeMFCyt/8IhBdqyDA8rdyewQMKZqvWT5Akg==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-jxa": "^3.0.0" + }, + "bin": { + "clipboard-image": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-5.3.1.tgz", + "integrity": "sha512-fPWgBqpp9ctiOQCkE5yjYGzv11ZU55g6ahEgr3COiio6dXdt1mbchCPXQrSR2Y9sZqfi8L7QD3+UosgXVIuPdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clipboard-image": "^0.1.0", + "execa": "^9.6.1", + "is-wayland": "^0.1.0", + "is-wsl": "^3.1.0", + "is64bit": "^2.0.0", + "powershell-utils": "^0.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/clipboardy/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/clipboardy/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/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/clipboardy/node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -11388,6 +13043,34 @@ "node": ">=0.8" } }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clone-deep/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/clone-stats": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", @@ -11435,6 +13118,16 @@ "node": ">= 0.12.0" } }, + "node_modules/cockatiel": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.2.1.tgz", + "integrity": "sha512-gfrHV6ZPkquExvMh9IOkKsBzNDk6sDuZ6DdBGUBkvFnTCqCxzpuq48RySgP0AnaqQkw2zynOFj9yly6T1Q2G5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/collect-v8-coverage": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", @@ -11572,6 +13265,13 @@ "license": "BSD-2-Clause", "peer": true }, + "node_modules/compare-versions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", + "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", + "dev": true, + "license": "MIT" + }, "node_modules/compute-gcd": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/compute-gcd/-/compute-gcd-1.2.1.tgz", @@ -12057,6 +13757,35 @@ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, + "node_modules/crypto-random-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/crypto-random-string/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cspell": { "version": "9.2.1", "resolved": "https://registry.npmjs.org/cspell/-/cspell-9.2.1.tgz", @@ -13758,6 +15487,23 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/editions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/editions/-/editions-6.22.0.tgz", + "integrity": "sha512-UgGlf8IW75je7HZjNDpJdCv4cGJWIi6yumFdZ0R7A8/CIhQiWUjyGLCxdHpd8bmyD1gnkfUNK0oeOXqUS2cpfQ==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "version-range": "^4.15.0" + }, + "engines": { + "ecmascript": ">= es5", + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -13835,6 +15581,34 @@ "iconv-lite": "^0.6.2" } }, + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/encoding-sniffer/node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -15607,6 +17381,35 @@ "node": "^12.20 || >= 14.13" } }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/figures/node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -15914,6 +17717,16 @@ "node": ">= 6" } }, + "node_modules/form-data-encoder": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-4.1.0.tgz", + "integrity": "sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, "node_modules/format-util": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/format-util/-/format-util-1.0.5.tgz", @@ -16582,6 +18395,85 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/got": { + "version": "14.6.6", + "resolved": "https://registry.npmjs.org/got/-/got-14.6.6.tgz", + "integrity": "sha512-QLV1qeYSo5l13mQzWgP/y0LbMr5Plr5fJilgAIwgnwseproEbtNym8xpLsDzeZ6MWXgNE6kdWGBjdh3zT/Qerg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^7.0.1", + "byte-counter": "^0.1.0", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^13.0.12", + "decompress-response": "^10.0.0", + "form-data-encoder": "^4.0.2", + "http2-wrapper": "^2.2.1", + "keyv": "^5.5.3", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^4.0.1", + "responselike": "^4.0.2", + "type-fest": "^4.26.1" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/got/node_modules/decompress-response": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-10.0.0.tgz", + "integrity": "sha512-oj7KWToJuuxlPr7VV0vabvxEIiqNMo+q0NueIiL3XhtwC6FVOX7Hr1c0C4eD0bmf7Zr+S/dSf2xvkH3Ad6sU3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^4.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/got/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, + "node_modules/got/node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/got/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -17221,6 +19113,16 @@ "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "license": "ISC" }, + "node_modules/hpagent": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.2.0.tgz", + "integrity": "sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/html-encoding-sniffer": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", @@ -17340,8 +19242,8 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", - "license": "BSD-2-Clause", - "optional": true + "devOptional": true, + "license": "BSD-2-Clause" }, "node_modules/http-errors": { "version": "2.0.1", @@ -17376,6 +19278,20 @@ "node": ">= 6" } }, + "node_modules/http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, "node_modules/https-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", @@ -17553,6 +19469,19 @@ "node": ">=8" } }, + "node_modules/index-to-position": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", + "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/infer-owner": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", @@ -18254,6 +20183,19 @@ "node": ">=0.10.0" } }, + "node_modules/is-wayland": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-wayland/-/is-wayland-0.1.0.tgz", + "integrity": "sha512-QkbMsWkIfkrzOPxenwye0h56iAXirZYHG9eHVPb22fO9y+wPbaX/CHacOWBa/I++4ohTcByimhM1/nyCsH8KNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-weakmap": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", @@ -18343,6 +20285,22 @@ "node": ">=v0.10.0" } }, + "node_modules/is64bit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is64bit/-/is64bit-2.0.0.tgz", + "integrity": "sha512-jv+8jaWCl0g2lSBkNSVXdzfBA0npK1HGC2KtWM9FumFRoGS94g3NbCCLVnCYHLjp4GrW2KZeeSTMo5ddtznmGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "system-architecture": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -18613,6 +20571,24 @@ "node": ">=8" } }, + "node_modules/istextorbinary": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-9.5.0.tgz", + "integrity": "sha512-5mbUj3SiZXCuRf9fT3ibzbSSEWiy63gFfksmGfdOzujPjW3k+z8WvIBxcJHBoQNlaZaiyB25deviif2+osLmLw==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "binaryextensions": "^6.11.0", + "editions": "^6.21.0", + "textextensions": "^6.11.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, "node_modules/iterator.prototype": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", @@ -20852,6 +22828,42 @@ "node": "*" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "dev": true, + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -20964,6 +22976,27 @@ "node": ">= 0.6" } }, + "node_modules/keytar": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz", + "integrity": "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-addon-api": "^4.3.0", + "prebuild-install": "^7.0.1" + } + }, + "node_modules/keytar/node_modules/node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -20978,6 +23011,16 @@ "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -21609,6 +23652,26 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "license": "MIT" }, + "node_modules/linkify-it": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.1.tgz", + "integrity": "sha512-wVoTjP4Q6R0NW5hiZkVJaFZPWgtXfoGF+6LucL3/FtiNjmcHhYjEr5f1Kqjirc1nBW07J/ZuRFumqr2oqccEWg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/lint-staged": { "version": "16.2.3", "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.3.tgz", @@ -21815,12 +23878,47 @@ "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", "dev": true }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", "license": "MIT" }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -21833,6 +23931,13 @@ "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", "license": "MIT" }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", @@ -22067,6 +24172,19 @@ "get-func-name": "^2.0.0" } }, + "node_modules/lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lru-cache": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", @@ -22090,6 +24208,35 @@ "url": "https://github.com/sponsors/wellwelwel" } }, + "node_modules/macos-version": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/macos-version/-/macos-version-6.0.0.tgz", + "integrity": "sha512-O2S8voA+pMfCHhBn/TIYDXzJ1qNHpPDU32oFxglKnVdJABiYYITt45oLkV9yhwA3E2FDwn3tQqUFrTsr1p3sBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/macos-version/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/magic-string": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.1.tgz", @@ -22194,6 +24341,54 @@ "node": ">=0.10.0" } }, + "node_modules/markdown-it": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.2.0.tgz", + "integrity": "sha512-1TGiQiJVRQ3NPmZH6sx5Cfnmg6GQm9jvC1ch4TK511NjSJvjzKLzn5pPfZRNZkRPZP0HqCioSndqH8v2nRaWVQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.1", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/markdown-it/node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, "node_modules/markdown-to-jsx": { "version": "7.7.17", "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.7.17.tgz", @@ -22429,7 +24624,6 @@ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true, - "optional": true, "bin": { "mime": "cli.js" }, @@ -22520,9 +24714,10 @@ } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -23045,6 +25240,13 @@ "node": ">= 10.13.0" } }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true, + "license": "ISC" + }, "node_modules/mysql2": { "version": "3.15.3", "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz", @@ -23544,6 +25746,58 @@ "integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==", "license": "MIT" }, + "node_modules/node-sarif-builder": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/node-sarif-builder/-/node-sarif-builder-3.4.0.tgz", + "integrity": "sha512-tGnJW6OKRii9u/b2WiUViTJS+h7Apxx17qsMUjsUeNDiMMX5ZFf8F8Fcz7PAQ6omvOxHZtvDTmOYKJQwmfpjeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/sarif": "^2.1.7", + "fs-extra": "^11.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/node-sarif-builder/node_modules/fs-extra": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz", + "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/node-sarif-builder/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/node-sarif-builder/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/node-ssh-forward": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/node-ssh-forward/-/node-ssh-forward-0.6.3.tgz", @@ -23712,6 +25966,19 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-url": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.1.tgz", + "integrity": "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/nouislider": { "version": "15.4.0", "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-15.4.0.tgz", @@ -24391,6 +26658,16 @@ "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", "dev": true }, + "node_modules/p-cancelable": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-4.0.1.tgz", + "integrity": "sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, "node_modules/p-each-series": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", @@ -24553,6 +26830,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parse-node-version": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", @@ -24571,6 +26861,16 @@ "node": ">=0.10.0" } }, + "node_modules/parse-semver": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/parse-semver/-/parse-semver-1.1.1.tgz", + "integrity": "sha512-Eg1OuNntBMH0ojvEKSrvDSnwLmvVuUOSdylH/pSCPNMIspLlweJyIWXCE+k/5hm3cj/EBUYwmWkjhBALNP4LXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^5.1.0" + } + }, "node_modules/parse-srcset": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", @@ -24583,6 +26883,85 @@ "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", "license": "MIT" }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parse5-parser-stream/node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -24781,6 +27160,13 @@ "node": ">=14" } }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -25085,6 +27471,16 @@ "node": ">=0.10.0" } }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/png-js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/png-js/-/png-js-1.0.0.tgz", @@ -25267,6 +27663,19 @@ "postinstall-build": "cli.js" } }, + "node_modules/powershell-utils": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.2.0.tgz", + "integrity": "sha512-ZlsFlG7MtSFCoc5xreOvBAozCJ6Pf06opgJjh9ONEv418xpZSAzNjstD36C6+JwOnfSqOW/9uDkqKjezTdxZhw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", @@ -25462,6 +27871,22 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "license": "MIT" }, + "node_modules/pretty-ms": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -25637,6 +28062,16 @@ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", "dev": true }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/pure-color": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/pure-color/-/pure-color-1.3.0.tgz", @@ -25693,6 +28128,19 @@ } ] }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/quote-stream": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/quote-stream/-/quote-stream-1.0.2.tgz", @@ -25788,6 +28236,19 @@ "rc": "cli.js" } }, + "node_modules/rc-config-loader": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/rc-config-loader/-/rc-config-loader-4.1.4.tgz", + "integrity": "sha512-3GiwEzklkbXTDp52UR5nT8iXgYAx1V9ZG/kDZT7p60u2GCv2XTwQq4NzinMoMpNtXhmt3WkhYXcj6HH8HdwCEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "js-yaml": "^4.1.1", + "json5": "^2.2.3", + "require-from-string": "^2.0.2" + } + }, "node_modules/rc/node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -26053,6 +28514,19 @@ "node": ">=6" } }, + "node_modules/read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "mute-stream": "~0.0.4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -26344,6 +28818,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, "node_modules/resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -26461,6 +28942,22 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/responselike": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-4.0.2.tgz", + "integrity": "sha512-cGk8IbWEAnaCpdAt1BHzJ3Ahz5ewDJa0KseTsE3qIRMJ3C698W8psM7byCeWVpd/Ha7FUYzuRVzXoKoM6nRUbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/restore-cursor": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", @@ -26803,6 +29300,85 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/run-jxa": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-jxa/-/run-jxa-3.0.0.tgz", + "integrity": "sha512-4f2CrY7H+sXkKXJn/cE6qRA3z+NMVO7zvlZ/nUV0e62yWftpiLAfw5eV9ZdomzWd2TXWwEIiGjAT57+lWIzzvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.1.1", + "macos-version": "^6.0.0", + "subsume": "^4.0.0", + "type-fest": "^2.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-jxa/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/run-jxa/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/run-jxa/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-jxa/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -27113,8 +29689,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true, - "optional": true + "dev": true }, "node_modules/saxes": { "version": "5.0.1", @@ -27166,6 +29741,251 @@ "temp": "^0.9.4" } }, + "node_modules/secretlint": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/secretlint/-/secretlint-10.2.2.tgz", + "integrity": "sha512-xVpkeHV/aoWe4vP4TansF622nBEImzCY73y/0042DuJ29iKIaqgoJ8fGxre3rVSHHbxar4FdJobmTnLp9AU0eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/config-creator": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/node": "^10.2.2", + "@secretlint/profiler": "^10.2.2", + "debug": "^4.4.1", + "globby": "^14.1.0", + "read-pkg": "^9.0.1" + }, + "bin": { + "secretlint": "bin/secretlint.js" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/secretlint/node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/secretlint/node_modules/globby": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/secretlint/node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/secretlint/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/secretlint/node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/secretlint/node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/secretlint/node_modules/path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/secretlint/node_modules/read-pkg": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", + "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.3", + "normalize-package-data": "^6.0.0", + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/secretlint/node_modules/read-pkg/node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/secretlint/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/secretlint/node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/secretlint/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/selenium-webdriver": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.45.0.tgz", + "integrity": "sha512-Cb2nqvJiwXVOtRTCYHX9D1FJR5+Ls7aL3Nev0t6n4CpXsQ//YGiiUmSCbvTDDeLtbV85SZ46qmLab4SIYKXWRw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/SeleniumHQ" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/selenium" + } + ], + "license": "Apache-2.0", + "dependencies": { + "@bazel/runfiles": "^6.5.0", + "jszip": "^3.10.1", + "tmp": "^0.2.7", + "ws": "^8.21.0" + }, + "engines": { + "node": ">= 20.0.0" + } + }, + "node_modules/selenium-webdriver/node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/semver": { "version": "5.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", @@ -27354,6 +30174,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/shallow-copy": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", @@ -28371,6 +31204,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/structured-source": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/structured-source/-/structured-source-4.0.0.tgz", + "integrity": "sha512-qGzRFNJDjFieQkl/sVOI2dUjHKRyL9dAJi2gCPGJLbJHBIkyOHxjuocpIEfbLioX+qSJpvbYdT49/YCdMznKxA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boundary": "^2.0.0" + } + }, "node_modules/stubs": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", @@ -28423,6 +31266,36 @@ "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", "license": "MIT" }, + "node_modules/subsume": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/subsume/-/subsume-4.0.0.tgz", + "integrity": "sha512-BWnYJElmHbYZ/zKevy+TG+SsyoFCmRPDHJbR1MzLxkPOv1Jp/4hGhVUtP98s+wZBsBsHwCXvPTP0x287/WMjGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^5.0.0", + "unique-string": "^3.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/subsume/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -28530,6 +31403,19 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/system-architecture": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/system-architecture/-/system-architecture-0.1.0.tgz", + "integrity": "sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tabbable": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.3.3.tgz", @@ -28723,6 +31609,100 @@ "node": ">=18" } }, + "node_modules/targz": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/targz/-/targz-1.0.1.tgz", + "integrity": "sha512-6q4tP9U55mZnRuMTBqnqc3nwYQY3kv+QthCFZuMk+Tn1qYUnMPmL/JZ/mzgXINzFpSqfU+242IFmFU9VPvqaQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tar-fs": "^1.8.1" + } + }, + "node_modules/targz/node_modules/bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/targz/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC" + }, + "node_modules/targz/node_modules/pump": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", + "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/targz/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/targz/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/targz/node_modules/tar-fs": { + "version": "1.16.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.6.tgz", + "integrity": "sha512-JkOgFt3FxM/2v2CNpAVHqMW2QASjc/Hxo7IGfNd3MHaDYSW/sBFiS7YVmmhmr8x6vwN1VFQDQGdT2MWpmIuVKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chownr": "^1.0.1", + "mkdirp": "^0.5.1", + "pump": "^1.0.0", + "tar-stream": "^1.1.2" + } + }, + "node_modules/targz/node_modules/tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/tas-client": { "version": "0.2.33", "resolved": "https://registry.npmjs.org/tas-client/-/tas-client-0.2.33.tgz", @@ -28966,6 +31946,22 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/textextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-6.11.0.tgz", + "integrity": "sha512-tXJwSr9355kFJI3lbCkPpUH5cP8/M0GGy2xLO34aZCjMXBaK3SoPnZwr/oWmo1FdCnELcs4npdCIOFtq9W3ruQ==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, "node_modules/throat": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", @@ -29654,6 +32650,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typed-rest-client": { + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.11.tgz", + "integrity": "sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "qs": "^6.9.1", + "tunnel": "0.0.6", + "underscore": "^1.12.1" + } + }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -29712,6 +32720,13 @@ "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==", "license": "MIT" }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, "node_modules/ufo": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.2.tgz", @@ -29833,6 +32848,19 @@ "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=" }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unique-filename": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", @@ -29853,6 +32881,22 @@ "imurmurhash": "^0.1.4" } }, + "node_modules/unique-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", + "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "crypto-random-string": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/universal-user-agent": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", @@ -29886,6 +32930,58 @@ "node": ">=8" } }, + "node_modules/unzipper": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.12.5.tgz", + "integrity": "sha512-tXYOi9R57Uj/2Z25SOs5RRSzq886MBQj2gY8dPL+xl/kv6s6SvByoKfAtvfVeEuhntWDgjd2o9p2lb4TVPAz0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "~3.7.2", + "duplexer2": "~0.1.4", + "fs-extra": "11.3.1", + "graceful-fs": "^4.2.2", + "node-int64": "^0.4.0" + } + }, + "node_modules/unzipper/node_modules/fs-extra": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz", + "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/unzipper/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/unzipper/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -29945,6 +33041,13 @@ "node": ">= 0.4" } }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true, + "license": "MIT" + }, "node_modules/url-parse": { "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", @@ -30866,6 +33969,19 @@ "integrity": "sha512-PGfp0m0QCufDmcxKJCWQy4Ov23FoF8DSXmoJwSezi3itQaa2hbxK0+xwsTMP2vy4PR16Pu25HMzgMwXVW1+33w==", "license": "BSD-3-Clause" }, + "node_modules/version-range": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.15.0.tgz", + "integrity": "sha512-Ck0EJbAGxHwprkzFO966t4/5QkRuzh+/I1RxhLgUKKwEn+Cd8NwM60mE3AqBZg5gYODoXW0EFsQvbZjRlvdqbg==", + "dev": true, + "license": "Artistic-2.0", + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, "node_modules/vinyl-contents": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/vinyl-contents/-/vinyl-contents-2.0.0.tgz", @@ -30925,6 +34041,355 @@ "integrity": "sha512-dzKWTMMyebIMPF1VYMuuQj7gGFq7guR8AFya0mKacu+ayptJfaRuM0mdHCqiOth4FnRP8mPhEroFPx6Ift8wHA==", "deprecated": "This package has been renamed to @vscode/debugprotocol, please update to the new name" }, + "node_modules/vscode-extension-tester": { + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/vscode-extension-tester/-/vscode-extension-tester-8.23.0.tgz", + "integrity": "sha512-YElhRDkOjvmGFv44aCMnEnUM5RmFqWByQ8TBr7/6N9K/b5o1gu2fo9EgTef6VmrB4kyFnSrkPVAbiZBfKQA1mQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@redhat-developer/locators": "^1.20.0", + "@redhat-developer/page-objects": "^1.20.0", + "@types/selenium-webdriver": "^4.35.5", + "@vscode/vsce": "^3.7.1", + "c8": "^11.0.0", + "commander": "^14.0.3", + "compare-versions": "^6.1.1", + "find-up": "8.0.0", + "fs-extra": "^11.3.4", + "glob": "^13.0.6", + "got": "^14.6.6", + "hpagent": "^1.2.0", + "js-yaml": "^4.1.1", + "sanitize-filename": "^1.6.3", + "selenium-webdriver": "^4.41.0", + "targz": "^1.0.1", + "unzipper": "^0.12.3" + }, + "bin": { + "extest": "out/cli.js" + }, + "peerDependencies": { + "mocha": ">=5.2.0", + "typescript": ">=4.6.2" + } + }, + "node_modules/vscode-extension-tester/node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/vscode-extension-tester/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/vscode-extension-tester/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/vscode-extension-tester/node_modules/c8": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/c8/-/c8-11.0.0.tgz", + "integrity": "sha512-e/uRViGHSVIJv7zsaDKM7VRn2390TgHXqUSvYwPHBQaU6L7E9L0n9JbdkwdYPvshDT0KymBmmlwSpms3yBaMNg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.1", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^8.0.0", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": "20 || >=22" + }, + "peerDependencies": { + "monocart-coverage-reports": "^2" + }, + "peerDependenciesMeta": { + "monocart-coverage-reports": { + "optional": true + } + } + }, + "node_modules/vscode-extension-tester/node_modules/c8/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vscode-extension-tester/node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/vscode-extension-tester/node_modules/find-up": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-8.0.0.tgz", + "integrity": "sha512-JGG8pvDi2C+JxidYdIwQDyS/CgcrIdh18cvgxcBge3wSHRQOrooMD3GlFBcmMJAN9M42SAZjDp5zv1dglJjwww==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^8.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vscode-extension-tester/node_modules/find-up/node_modules/locate-path": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-8.0.0.tgz", + "integrity": "sha512-XT9ewWAC43tiAV7xDAPflMkG0qOPn2QjHqlgX8FOqmWa/rxnyYDulF9T0F7tRy1u+TVTmK/M//6VIOye+2zDXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vscode-extension-tester/node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/vscode-extension-tester/node_modules/fs-extra": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz", + "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/vscode-extension-tester/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/vscode-extension-tester/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/vscode-extension-tester/node_modules/lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/vscode-extension-tester/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/vscode-extension-tester/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vscode-extension-tester/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vscode-extension-tester/node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/vscode-extension-tester/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/vscode-extension-tester/node_modules/test-exclude": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-8.0.0.tgz", + "integrity": "sha512-ZOffsNrXYggvU1mDGHk54I96r26P8SyMjO5slMKSc7+IWmtB/MQKnEC2fP51imB3/pT6YK5cT5E8f+Dd9KdyOQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^13.0.6", + "minimatch": "^10.2.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/vscode-extension-tester/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/vscode-extension-tester/node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/vscode-jsonrpc": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", @@ -31423,6 +34888,38 @@ "async-limiter": "~1.0.0" } }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wsl-utils/node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/xdg-basedir": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", @@ -31448,6 +34945,30 @@ "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", "license": "Apache-2.0" }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, "node_modules/xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", @@ -31592,6 +35113,29 @@ "node": ">=12" } }, + "node_modules/yauzl": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.4.0.tgz", + "integrity": "sha512-jIH9yLR9wqr0wOS0TpBvo/g/2UgZH5qePVbjgRliiF0BYvOZyaBknKsF+x9Iht0O6sqgnB93rCICdOZFecJuDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yazl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", + "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3" + } + }, "node_modules/yjs": { "version": "13.6.29", "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.29.tgz", @@ -31757,6 +35301,21 @@ "tinyexec": "^1.0.1" } }, + "@azu/format-text": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@azu/format-text/-/format-text-1.0.2.tgz", + "integrity": "sha512-Swi4N7Edy1Eqq82GxgEECXSSLyn6GOb5htRFPzBDdUkECGXtlf12ynO5oJSpWKPwCaUssOu7NfhDcCWpIC6Ywg==", + "dev": true + }, + "@azu/style-format": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@azu/style-format/-/style-format-1.0.1.tgz", + "integrity": "sha512-AHcTojlNBdD/3/KxIKlg8sxIWHfOtQszLvOpagLTO+bjC3u7SAszu1lf//u7JJC50aUSH+BVWDD/KvaA6Gfn5g==", + "dev": true, + "requires": { + "@azu/format-text": "^1.0.1" + } + }, "@azure/abort-controller": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", @@ -31775,6 +35334,21 @@ "tslib": "^2.6.2" } }, + "@azure/core-client": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.2.tgz", + "integrity": "sha512-1D2LpsU7y9xrqKjdIbsB7PlrRePw0xsVV8p+AKTlzITrWmscajryfJCdDJB/oGwvDI5HmRo04eMMADB67uwAwQ==", + "dev": true, + "requires": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-rest-pipeline": "^1.22.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "tslib": "^2.6.2" + } + }, "@azure/core-rest-pipeline": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.22.1.tgz", @@ -31807,6 +35381,70 @@ "tslib": "^2.6.2" } }, + "@azure/identity": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.13.1.tgz", + "integrity": "sha512-5C/2WD5Vb1lHnZS16dNQRPMjN6oV/Upba+C9nBIs15PmOi6A3ZGs4Lr2u60zw4S04gi+u3cEXiqTVP7M4Pz3kw==", + "dev": true, + "requires": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.9.0", + "@azure/core-client": "^1.9.2", + "@azure/core-rest-pipeline": "^1.17.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.11.0", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^5.5.0", + "@azure/msal-node": "^5.1.0", + "open": "^10.1.0", + "tslib": "^2.2.0" + }, + "dependencies": { + "bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "requires": { + "run-applescript": "^7.0.0" + } + }, + "default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "dev": true, + "requires": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + } + }, + "default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true + }, + "open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "requires": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + } + }, + "run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true + } + } + }, "@azure/logger": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.3.0.tgz", @@ -31816,6 +35454,31 @@ "tslib": "^2.6.2" } }, + "@azure/msal-browser": { + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.14.0.tgz", + "integrity": "sha512-Dfl7hPZe9/JJwRhFFXHq2z1oHYBuGubmff3kWXOsd1AGgyXlqjNYAWuN/1JL/ZrcZBs8TKMjGSil6Rcc7E8VPQ==", + "dev": true, + "requires": { + "@azure/msal-common": "16.9.0" + } + }, + "@azure/msal-common": { + "version": "16.9.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.9.0.tgz", + "integrity": "sha512-1MWGjqgUCRAYgLmVFZKp7fs3Rg1TFvIMgywY8ze2olNVvLlJoRThuoziWSDJuwwyJI5L4rnLb9Tyt5D9GvSLPw==", + "dev": true + }, + "@azure/msal-node": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-5.2.5.tgz", + "integrity": "sha512-RUuewWk9JvWJS5Yiy8/74Lm1rQAWlrU/qg/Bgtk1jIauVRtnb9XKwS5Xg0J+Whwjesq9EVrBIFgQEP8vHxgezA==", + "dev": true, + "requires": { + "@azure/msal-common": "16.9.0", + "jsonwebtoken": "^9.0.0" + } + }, "@babel/code-frame": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", @@ -32146,6 +35809,12 @@ "@babel/helper-validator-identifier": "^7.29.7" } }, + "@bazel/runfiles": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@bazel/runfiles/-/runfiles-6.5.0.tgz", + "integrity": "sha512-RzahvqTkfpY2jsDxo8YItPX+/iZ6hbiikw1YhE0bA9EKBR5Og8Pa6FHn9PO9M0zaXRVsr0GFQLKbB/0rzy9SzA==", + "dev": true + }, "@bcoe/v8-coverage": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", @@ -35544,6 +39213,12 @@ } } }, + "@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "dev": true + }, "@koa/cors": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@koa/cors/-/cors-5.0.0.tgz", @@ -36413,6 +40088,61 @@ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", "peer": true }, + "@redhat-developer/locators": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/@redhat-developer/locators/-/locators-1.20.0.tgz", + "integrity": "sha512-SvS9lABIHDpTU0w7jTN3qsMxDSceAbbc3j0xba9Cd8xrDEMWpysnvt3WY4vtYalikMhXtkcSAf3L3adYTS7iqg==", + "dev": true, + "requires": {} + }, + "@redhat-developer/page-objects": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/@redhat-developer/page-objects/-/page-objects-1.20.0.tgz", + "integrity": "sha512-akY6gXRbUmlf2Cayp1A/TwoT0/eyK+/LReSBo7Nmfotk7QfR262k7ZilAmL3Vf/jmX3Gg0D8UrDZRFk8RTxS2Q==", + "dev": true, + "requires": { + "clipboardy": "^5.3.1", + "clone-deep": "^4.0.1", + "compare-versions": "^6.1.1", + "fs-extra": "^11.3.4", + "type-fest": "^4.41.0" + }, + "dependencies": { + "fs-extra": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz", + "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true + }, + "universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true + } + } + }, "@rjsf/utils": { "version": "5.24.13", "resolved": "https://registry.npmjs.org/@rjsf/utils/-/utils-5.24.13.tgz", @@ -36425,11 +40155,243 @@ "react-is": "^18.2.0" } }, + "@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true + }, + "@secretlint/config-creator": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/config-creator/-/config-creator-10.2.2.tgz", + "integrity": "sha512-BynOBe7Hn3LJjb3CqCHZjeNB09s/vgf0baBaHVw67w7gHF0d25c3ZsZ5+vv8TgwSchRdUCRrbbcq5i2B1fJ2QQ==", + "dev": true, + "requires": { + "@secretlint/types": "^10.2.2" + } + }, + "@secretlint/config-loader": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/config-loader/-/config-loader-10.2.2.tgz", + "integrity": "sha512-ndjjQNgLg4DIcMJp4iaRD6xb9ijWQZVbd9694Ol2IszBIbGPPkwZHzJYKICbTBmh6AH/pLr0CiCaWdGJU7RbpQ==", + "dev": true, + "requires": { + "@secretlint/profiler": "^10.2.2", + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", + "ajv": "^8.17.1", + "debug": "^4.4.1", + "rc-config-loader": "^4.1.3" + }, + "dependencies": { + "ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.1.2", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } + } + }, + "@secretlint/core": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/core/-/core-10.2.2.tgz", + "integrity": "sha512-6rdwBwLP9+TO3rRjMVW1tX+lQeo5gBbxl1I5F8nh8bgGtKwdlCMhMKsBWzWg1ostxx/tIG7OjZI0/BxsP8bUgw==", + "dev": true, + "requires": { + "@secretlint/profiler": "^10.2.2", + "@secretlint/types": "^10.2.2", + "debug": "^4.4.1", + "structured-source": "^4.0.0" + } + }, + "@secretlint/formatter": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/formatter/-/formatter-10.2.2.tgz", + "integrity": "sha512-10f/eKV+8YdGKNQmoDUD1QnYL7TzhI2kzyx95vsJKbEa8akzLAR5ZrWIZ3LbcMmBLzxlSQMMccRmi05yDQ5YDA==", + "dev": true, + "requires": { + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", + "@textlint/linter-formatter": "^15.2.0", + "@textlint/module-interop": "^15.2.0", + "@textlint/types": "^15.2.0", + "chalk": "^5.4.1", + "debug": "^4.4.1", + "pluralize": "^8.0.0", + "strip-ansi": "^7.1.0", + "table": "^6.9.0", + "terminal-link": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true + }, + "chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "requires": { + "ansi-regex": "^6.2.2" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "dev": true, + "requires": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + } + }, + "terminal-link": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-4.0.0.tgz", + "integrity": "sha512-lk+vH+MccxNqgVqSnkMVKx4VLJfnLjDBGzH16JVZjKE2DoxP57s6/vt6JmXV5I3jBcfGrxNrYtC+mPtU7WJztA==", + "dev": true, + "requires": { + "ansi-escapes": "^7.0.0", + "supports-hyperlinks": "^3.2.0" + } + } + } + }, + "@secretlint/node": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/node/-/node-10.2.2.tgz", + "integrity": "sha512-eZGJQgcg/3WRBwX1bRnss7RmHHK/YlP/l7zOQsrjexYt6l+JJa5YhUmHbuGXS94yW0++3YkEJp0kQGYhiw1DMQ==", + "dev": true, + "requires": { + "@secretlint/config-loader": "^10.2.2", + "@secretlint/core": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/profiler": "^10.2.2", + "@secretlint/source-creator": "^10.2.2", + "@secretlint/types": "^10.2.2", + "debug": "^4.4.1", + "p-map": "^7.0.3" + }, + "dependencies": { + "p-map": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", + "dev": true + } + } + }, + "@secretlint/profiler": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/profiler/-/profiler-10.2.2.tgz", + "integrity": "sha512-qm9rWfkh/o8OvzMIfY8a5bCmgIniSpltbVlUVl983zDG1bUuQNd1/5lUEeWx5o/WJ99bXxS7yNI4/KIXfHexig==", + "dev": true + }, + "@secretlint/resolver": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/resolver/-/resolver-10.2.2.tgz", + "integrity": "sha512-3md0cp12e+Ae5V+crPQYGd6aaO7ahw95s28OlULGyclyyUtf861UoRGS2prnUrKh7MZb23kdDOyGCYb9br5e4w==", + "dev": true + }, + "@secretlint/secretlint-formatter-sarif": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-formatter-sarif/-/secretlint-formatter-sarif-10.2.2.tgz", + "integrity": "sha512-ojiF9TGRKJJw308DnYBucHxkpNovDNu1XvPh7IfUp0A12gzTtxuWDqdpuVezL7/IP8Ua7mp5/VkDMN9OLp1doQ==", + "dev": true, + "requires": { + "node-sarif-builder": "^3.2.0" + } + }, + "@secretlint/secretlint-rule-no-dotenv": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-no-dotenv/-/secretlint-rule-no-dotenv-10.2.2.tgz", + "integrity": "sha512-KJRbIShA9DVc5Va3yArtJ6QDzGjg3PRa1uYp9As4RsyKtKSSZjI64jVca57FZ8gbuk4em0/0Jq+uy6485wxIdg==", + "dev": true, + "requires": { + "@secretlint/types": "^10.2.2" + } + }, + "@secretlint/secretlint-rule-preset-recommend": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-preset-recommend/-/secretlint-rule-preset-recommend-10.2.2.tgz", + "integrity": "sha512-K3jPqjva8bQndDKJqctnGfwuAxU2n9XNCPtbXVI5JvC7FnQiNg/yWlQPbMUlBXtBoBGFYp08A94m6fvtc9v+zA==", + "dev": true + }, + "@secretlint/source-creator": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/source-creator/-/source-creator-10.2.2.tgz", + "integrity": "sha512-h6I87xJfwfUTgQ7irWq7UTdq/Bm1RuQ/fYhA3dtTIAop5BwSFmZyrchph4WcoEvbN460BWKmk4RYSvPElIIvxw==", + "dev": true, + "requires": { + "@secretlint/types": "^10.2.2", + "istextorbinary": "^9.5.0" + } + }, + "@secretlint/types": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/types/-/types-10.2.2.tgz", + "integrity": "sha512-Nqc90v4lWCXyakD6xNyNACBJNJ0tNCwj2WNk/7ivyacYHxiITVgmLUFXTBOeCdy79iz6HtN9Y31uw/jbLrdOAg==", + "dev": true + }, "@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" }, + "@sindresorhus/is": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz", + "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==", + "dev": true + }, + "@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true + }, "@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -36683,6 +40645,112 @@ "tailwindcss": "4.1.14" } }, + "@textlint/ast-node-types": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-15.7.1.tgz", + "integrity": "sha512-Wii5UgUKFEh9Uv6wbq1zr4/Kf+dtjiUuzPrrXzKp8H+ifkvKNzi23V4Nz+6wVyHQn5T28AFuc8VH8OtzvGYecA==", + "dev": true + }, + "@textlint/linter-formatter": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/linter-formatter/-/linter-formatter-15.7.1.tgz", + "integrity": "sha512-TdwZ/debWYFD05K3CcoHtwvnCrza29wZxD+BjDTk/V5N7iRqkK1dTTHSD4A8AIgROLiDkHJmIKQbasbmsg8AvA==", + "dev": true, + "requires": { + "@azu/format-text": "^1.0.2", + "@azu/style-format": "^1.0.1", + "@textlint/module-interop": "15.7.1", + "@textlint/resolver": "15.7.1", + "@textlint/types": "15.7.1", + "chalk": "^4.1.2", + "debug": "^4.4.3", + "js-yaml": "^4.1.1", + "lodash": "^4.18.1", + "pluralize": "^2.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "table": "^6.9.0", + "text-table": "^0.2.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "pluralize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-2.0.0.tgz", + "integrity": "sha512-TqNZzQCD4S42De9IfnnBvILN7HAW7riLqsCyp8lgjXeysyPlX5HhqKAcJHHHb9XskE4/a+7VGC9zzx8Ls0jOAw==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@textlint/module-interop": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/module-interop/-/module-interop-15.7.1.tgz", + "integrity": "sha512-Jg+sQW2L/cRJypk59wtcMUVVpt8vmit5ZMT3gUnFwevP3A6Qp1HfOtUy9ObT4hBX3lOSGT/ekcCDxR1pL7uH1g==", + "dev": true + }, + "@textlint/resolver": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/resolver/-/resolver-15.7.1.tgz", + "integrity": "sha512-8XnO0pgF6mXnm41VvWmBbEIdGPhiCUt31uLZkOis1ECeg/1SoUcIT6Mx/F0e1rukq8l0UlOSeY9a31CsvRMK0g==", + "dev": true + }, + "@textlint/types": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/types/-/types-15.7.1.tgz", + "integrity": "sha512-Vye/GmFNBTgVzZFtIFJTmLB+s2A7oIADxNG6r9UhfPuY+Czv0z5G3xeyFZZudPlfxURsKUyPIU5XsjOFqVp33A==", + "dev": true, + "requires": { + "@textlint/ast-node-types": "15.7.1" + } + }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -37125,6 +41193,12 @@ "hoist-non-react-statics": "^3.3.0" } }, + "@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "dev": true + }, "@types/http-errors": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", @@ -37393,11 +41467,27 @@ "redux": "^4.0.0" } }, + "@types/sarif": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@types/sarif/-/sarif-2.1.7.tgz", + "integrity": "sha512-kRz0VEkJqWLf1LLVN4pT1cg1Z9wAuvI6L97V3m2f5B76Tg8d413ddvLBPTEHAZJlnn4XSvu0FkZtViCQGVyrXQ==", + "dev": true + }, "@types/scheduler": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, + "@types/selenium-webdriver": { + "version": "4.35.6", + "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-4.35.6.tgz", + "integrity": "sha512-8nfyMRi4VvkY9QrQGyY/zkleAhnjnmE8YtdEeoCrWe3izp1P9vo9f5VTNRYF0up+l+kn+VuZah+je+bLddNV+g==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/ws": "*" + } + }, "@types/semver": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.5.0.tgz", @@ -38106,6 +42196,258 @@ } } }, + "@vscode/vsce": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.9.2.tgz", + "integrity": "sha512-XSxMosEEDO6vLxELAHVkwmhC0qe0ijZni2jB9Rcs8kQsW4lhTDQ/wMzmwFs/buotAWSnpmUp/dRWD2ufG3UYKA==", + "dev": true, + "requires": { + "@azure/identity": "^4.1.0", + "@secretlint/node": "^10.1.2", + "@secretlint/secretlint-formatter-sarif": "^10.1.2", + "@secretlint/secretlint-rule-no-dotenv": "^10.1.2", + "@secretlint/secretlint-rule-preset-recommend": "^10.1.2", + "@vscode/vsce-sign": "^2.0.0", + "azure-devops-node-api": "^12.5.0", + "chalk": "^4.1.2", + "cheerio": "^1.0.0-rc.9", + "cockatiel": "^3.1.2", + "commander": "^12.1.0", + "form-data": "4.0.6", + "glob": "^13.0.6", + "hosted-git-info": "^4.0.2", + "jsonc-parser": "^3.2.0", + "keytar": "^7.7.0", + "leven": "^3.1.0", + "markdown-it": "^14.1.0", + "mime": "^1.3.4", + "minimatch": "^10.2.2", + "parse-semver": "^1.1.1", + "read": "^1.0.7", + "secretlint": "^10.1.2", + "semver": "^7.5.2", + "tmp": "^0.2.3", + "typed-rest-client": "^1.8.4", + "url-join": "^4.0.1", + "xml2js": "^0.5.0", + "yauzl": "^3.2.1", + "yazl": "^2.2.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true + }, + "brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "requires": { + "balanced-match": "^4.0.2" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true + }, + "glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "requires": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "requires": { + "brace-expansion": "^5.0.5" + } + }, + "path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "requires": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "dependencies": { + "lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "dev": true + } + } + }, + "semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@vscode/vsce-sign": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign/-/vsce-sign-2.0.9.tgz", + "integrity": "sha512-8IvaRvtFyzUnGGl3f5+1Cnor3LqaUWvhaUjAYO8Y39OUYlOf3cRd+dowuQYLpZcP3uwSG+mURwjEBOSq4SOJ0g==", + "dev": true, + "requires": { + "@vscode/vsce-sign-alpine-arm64": "2.0.6", + "@vscode/vsce-sign-alpine-x64": "2.0.6", + "@vscode/vsce-sign-darwin-arm64": "2.0.6", + "@vscode/vsce-sign-darwin-x64": "2.0.6", + "@vscode/vsce-sign-linux-arm": "2.0.6", + "@vscode/vsce-sign-linux-arm64": "2.0.6", + "@vscode/vsce-sign-linux-x64": "2.0.6", + "@vscode/vsce-sign-win32-arm64": "2.0.6", + "@vscode/vsce-sign-win32-x64": "2.0.6" + } + }, + "@vscode/vsce-sign-alpine-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-arm64/-/vsce-sign-alpine-arm64-2.0.6.tgz", + "integrity": "sha512-wKkJBsvKF+f0GfsUuGT0tSW0kZL87QggEiqNqK6/8hvqsXvpx8OsTEc3mnE1kejkh5r+qUyQ7PtF8jZYN0mo8Q==", + "dev": true, + "optional": true + }, + "@vscode/vsce-sign-alpine-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-x64/-/vsce-sign-alpine-x64-2.0.6.tgz", + "integrity": "sha512-YoAGlmdK39vKi9jA18i4ufBbd95OqGJxRvF3n6ZbCyziwy3O+JgOpIUPxv5tjeO6gQfx29qBivQ8ZZTUF2Ba0w==", + "dev": true, + "optional": true + }, + "@vscode/vsce-sign-darwin-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-arm64/-/vsce-sign-darwin-arm64-2.0.6.tgz", + "integrity": "sha512-5HMHaJRIQuozm/XQIiJiA0W9uhdblwwl2ZNDSSAeXGO9YhB9MH5C4KIHOmvyjUnKy4UCuiP43VKpIxW1VWP4tQ==", + "dev": true, + "optional": true + }, + "@vscode/vsce-sign-darwin-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-x64/-/vsce-sign-darwin-x64-2.0.6.tgz", + "integrity": "sha512-25GsUbTAiNfHSuRItoQafXOIpxlYj+IXb4/qarrXu7kmbH94jlm5sdWSCKrrREs8+GsXF1b+l3OB7VJy5jsykw==", + "dev": true, + "optional": true + }, + "@vscode/vsce-sign-linux-arm": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm/-/vsce-sign-linux-arm-2.0.6.tgz", + "integrity": "sha512-UndEc2Xlq4HsuMPnwu7420uqceXjs4yb5W8E2/UkaHBB9OWCwMd3/bRe/1eLe3D8kPpxzcaeTyXiK3RdzS/1CA==", + "dev": true, + "optional": true + }, + "@vscode/vsce-sign-linux-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm64/-/vsce-sign-linux-arm64-2.0.6.tgz", + "integrity": "sha512-cfb1qK7lygtMa4NUl2582nP7aliLYuDEVpAbXJMkDq1qE+olIw/es+C8j1LJwvcRq1I2yWGtSn3EkDp9Dq5FdA==", + "dev": true, + "optional": true + }, + "@vscode/vsce-sign-linux-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-x64/-/vsce-sign-linux-x64-2.0.6.tgz", + "integrity": "sha512-/olerl1A4sOqdP+hjvJ1sbQjKN07Y3DVnxO4gnbn/ahtQvFrdhUi0G1VsZXDNjfqmXw57DmPi5ASnj/8PGZhAA==", + "dev": true, + "optional": true + }, + "@vscode/vsce-sign-win32-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-arm64/-/vsce-sign-win32-arm64-2.0.6.tgz", + "integrity": "sha512-ivM/MiGIY0PJNZBoGtlRBM/xDpwbdlCWomUWuLmIxbi1Cxe/1nooYrEQoaHD8ojVRgzdQEUzMsRbyF5cJJgYOg==", + "dev": true, + "optional": true + }, + "@vscode/vsce-sign-win32-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-x64/-/vsce-sign-win32-x64-2.0.6.tgz", + "integrity": "sha512-mgth9Kvze+u8CruYMmhHw6Zgy3GRX2S+Ed5oSokDEK5vPEwGGKnmuXua9tmFhomeAnhgJnL4DCna3TiNuGrBTQ==", + "dev": true, + "optional": true + }, "@vscode/zeromq": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/@vscode/zeromq/-/zeromq-0.2.7.tgz", @@ -38749,6 +43091,16 @@ "dequal": "^2.0.3" } }, + "azure-devops-node-api": { + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-12.5.0.tgz", + "integrity": "sha512-R5eFskGvOm3U/GzeAuxRkUsAl0hrAwGgWn6zAd2KrZmrEhWZVqLew4OOupbQlXUuojUzpGtq62SmdhJ06N88og==", + "dev": true, + "requires": { + "tunnel": "0.0.6", + "typed-rest-client": "^1.8.4" + } + }, "b4a": { "version": "1.6.6", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", @@ -39080,6 +43432,15 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, + "binaryextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-6.11.0.tgz", + "integrity": "sha512-sXnYK/Ij80TO3lcqZVV2YgfKN5QjUWIRk/XSm2J/4bd/lPko3lvk0O4ZppH6m+6hB2/GTu+ptNwVFe1xh+QLQw==", + "dev": true, + "requires": { + "editions": "^6.21.0" + } + }, "bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -39099,6 +43460,12 @@ "readable-stream": "^3.4.0" } }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, "bn.js": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.3.tgz", @@ -39156,6 +43523,12 @@ "resolved": "https://registry.npmjs.org/bootstrap-less/-/bootstrap-less-3.3.8.tgz", "integrity": "sha1-cfKd1af//t/onxYFu63+CjONrlM=" }, + "boundary": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/boundary/-/boundary-2.0.0.tgz", + "integrity": "sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA==", + "dev": true + }, "bplist-parser": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", @@ -39372,6 +43745,28 @@ "ieee754": "^1.2.1" } }, + "buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dev": true, + "requires": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "dev": true + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true + }, "buffer-equal": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", @@ -39382,6 +43777,12 @@ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, + "buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", + "dev": true + }, "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -39429,6 +43830,12 @@ "run-applescript": "^5.0.0" } }, + "byte-counter": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/byte-counter/-/byte-counter-0.1.0.tgz", + "integrity": "sha512-jheRLVMeUKrDBjVw2O5+k4EvR4t9wtxHL+bo/LxfkxsVeuGMy3a5SEGgXdAFA4FSzTrU8rQXQIrsZ3oBq5a0pQ==", + "dev": true + }, "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -39561,6 +43968,60 @@ } } }, + "cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "dev": true + }, + "cacheable-request": { + "version": "13.0.19", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-13.0.19.tgz", + "integrity": "sha512-SVXGH037+Mo1aIMO5B2UcleR43FGjFdN+M8JObSyEoQ2Mn4CODRWx28gN5jiTF0n5ItsgtIZfyargMNs8GX4kg==", + "dev": true, + "requires": { + "@types/http-cache-semantics": "^4.2.0", + "get-stream": "^9.0.1", + "http-cache-semantics": "^4.2.0", + "keyv": "^5.6.0", + "mimic-response": "^4.0.0", + "normalize-url": "^8.1.1", + "responselike": "^4.0.2" + }, + "dependencies": { + "get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "requires": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + } + }, + "is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true + }, + "keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "requires": { + "@keyv/serialize": "^1.1.1" + } + }, + "mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "dev": true + } + } + }, "caching-transform": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", @@ -39745,6 +44206,88 @@ "get-func-name": "^2.0.2" } }, + "cheerio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", + "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", + "dev": true, + "requires": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.1.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.19.0", + "whatwg-mimetype": "^4.0.0" + }, + "dependencies": { + "entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true + }, + "htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "dev": true, + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "requires": { + "entities": "^6.0.0" + }, + "dependencies": { + "entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true + } + } + }, + "undici": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", + "dev": true + }, + "whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true + } + } + }, + "cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + } + }, "chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -39900,6 +44443,116 @@ } } }, + "clipboard-image": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/clipboard-image/-/clipboard-image-0.1.0.tgz", + "integrity": "sha512-SWk7FgaXLNFld19peQ/rTe0n97lwR1WbkqxV6JKCAOh7U52AKV/PeMFCyt/8IhBdqyDA8rdyewQMKZqvWT5Akg==", + "dev": true, + "requires": { + "run-jxa": "^3.0.0" + } + }, + "clipboardy": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-5.3.1.tgz", + "integrity": "sha512-fPWgBqpp9ctiOQCkE5yjYGzv11ZU55g6ahEgr3COiio6dXdt1mbchCPXQrSR2Y9sZqfi8L7QD3+UosgXVIuPdg==", + "dev": true, + "requires": { + "clipboard-image": "^0.1.0", + "execa": "^9.6.1", + "is-wayland": "^0.1.0", + "is-wsl": "^3.1.0", + "is64bit": "^2.0.0", + "powershell-utils": "^0.2.0" + }, + "dependencies": { + "execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "dev": true, + "requires": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + } + }, + "get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "requires": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + } + }, + "human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true + }, + "is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true + }, + "is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true + }, + "is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "requires": { + "is-inside-container": "^1.0.0" + } + }, + "npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, + "requires": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + } + }, + "path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "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 + }, + "strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true + } + } + }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -39916,6 +44569,28 @@ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "dependencies": { + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + } + } + }, "clone-stats": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", @@ -39947,6 +44622,12 @@ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==" }, + "cockatiel": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.2.1.tgz", + "integrity": "sha512-gfrHV6ZPkquExvMh9IOkKsBzNDk6sDuZ6DdBGUBkvFnTCqCxzpuq48RySgP0AnaqQkw2zynOFj9yly6T1Q2G5Q==", + "dev": true + }, "collect-v8-coverage": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", @@ -40054,6 +44735,12 @@ "xss-filters": "^1.2.6" } }, + "compare-versions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", + "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", + "dev": true + }, "compute-gcd": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/compute-gcd/-/compute-gcd-1.2.1.tgz", @@ -40444,6 +45131,23 @@ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, + "crypto-random-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "dev": true, + "requires": { + "type-fest": "^1.0.1" + }, + "dependencies": { + "type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true + } + } + }, "cspell": { "version": "9.2.1", "resolved": "https://registry.npmjs.org/cspell/-/cspell-9.2.1.tgz", @@ -41670,6 +46374,15 @@ "safe-buffer": "^5.0.1" } }, + "editions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/editions/-/editions-6.22.0.tgz", + "integrity": "sha512-UgGlf8IW75je7HZjNDpJdCv4cGJWIi6yumFdZ0R7A8/CIhQiWUjyGLCxdHpd8bmyD1gnkfUNK0oeOXqUS2cpfQ==", + "dev": true, + "requires": { + "version-range": "^4.15.0" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -41734,6 +46447,27 @@ "iconv-lite": "^0.6.2" } }, + "encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "dev": true, + "requires": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "dependencies": { + "whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "requires": { + "iconv-lite": "0.6.3" + } + } + } + }, "end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -43035,6 +47769,23 @@ "web-streams-polyfill": "^3.0.3" } }, + "figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dev": true, + "requires": { + "is-unicode-supported": "^2.0.0" + }, + "dependencies": { + "is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true + } + } + }, "file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -43256,6 +48007,12 @@ "mime-types": "^2.1.35" } }, + "form-data-encoder": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-4.1.0.tgz", + "integrity": "sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw==", + "dev": true + }, "format-util": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/format-util/-/format-util-1.0.5.tgz", @@ -43711,6 +48468,58 @@ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" }, + "got": { + "version": "14.6.6", + "resolved": "https://registry.npmjs.org/got/-/got-14.6.6.tgz", + "integrity": "sha512-QLV1qeYSo5l13mQzWgP/y0LbMr5Plr5fJilgAIwgnwseproEbtNym8xpLsDzeZ6MWXgNE6kdWGBjdh3zT/Qerg==", + "dev": true, + "requires": { + "@sindresorhus/is": "^7.0.1", + "byte-counter": "^0.1.0", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^13.0.12", + "decompress-response": "^10.0.0", + "form-data-encoder": "^4.0.2", + "http2-wrapper": "^2.2.1", + "keyv": "^5.5.3", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^4.0.1", + "responselike": "^4.0.2", + "type-fest": "^4.26.1" + }, + "dependencies": { + "decompress-response": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-10.0.0.tgz", + "integrity": "sha512-oj7KWToJuuxlPr7VV0vabvxEIiqNMo+q0NueIiL3XhtwC6FVOX7Hr1c0C4eD0bmf7Zr+S/dSf2xvkH3Ad6sU3Q==", + "dev": true, + "requires": { + "mimic-response": "^4.0.0" + } + }, + "keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "requires": { + "@keyv/serialize": "^1.1.1" + } + }, + "mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "dev": true + }, + "type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true + } + } + }, "graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -44191,6 +49000,12 @@ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" }, + "hpagent": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.2.0.tgz", + "integrity": "sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==", + "dev": true + }, "html-encoding-sniffer": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", @@ -44268,7 +49083,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", - "optional": true + "devOptional": true }, "http-errors": { "version": "2.0.1", @@ -44292,6 +49107,16 @@ "debug": "4" } }, + "http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", + "dev": true, + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + } + }, "https-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", @@ -44399,6 +49224,12 @@ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "devOptional": true }, + "index-to-position": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", + "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", + "dev": true + }, "infer-owner": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", @@ -44871,6 +49702,12 @@ "integrity": "sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA==", "dev": true }, + "is-wayland": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-wayland/-/is-wayland-0.1.0.tgz", + "integrity": "sha512-QkbMsWkIfkrzOPxenwye0h56iAXirZYHG9eHVPb22fO9y+wPbaX/CHacOWBa/I++4ohTcByimhM1/nyCsH8KNA==", + "dev": true + }, "is-weakmap": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", @@ -44935,6 +49772,15 @@ "is-url": "^1.2.4" } }, + "is64bit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is64bit/-/is64bit-2.0.0.tgz", + "integrity": "sha512-jv+8jaWCl0g2lSBkNSVXdzfBA0npK1HGC2KtWM9FumFRoGS94g3NbCCLVnCYHLjp4GrW2KZeeSTMo5ddtznmGw==", + "dev": true, + "requires": { + "system-architecture": "^0.1.0" + } + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -45129,6 +49975,17 @@ "istanbul-lib-report": "^3.0.0" } }, + "istextorbinary": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-9.5.0.tgz", + "integrity": "sha512-5mbUj3SiZXCuRf9fT3ibzbSSEWiy63gFfksmGfdOzujPjW3k+z8WvIBxcJHBoQNlaZaiyB25deviif2+osLmLw==", + "dev": true, + "requires": { + "binaryextensions": "^6.11.0", + "editions": "^6.21.0", + "textextensions": "^6.11.0" + } + }, "iterator.prototype": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", @@ -46654,6 +51511,32 @@ "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==", "dev": true }, + "jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "dev": true, + "requires": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "dependencies": { + "semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true + } + } + }, "jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -46750,6 +51633,26 @@ "tsscmp": "1.0.6" } }, + "keytar": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz", + "integrity": "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ==", + "dev": true, + "optional": true, + "requires": { + "node-addon-api": "^4.3.0", + "prebuild-install": "^7.0.1" + }, + "dependencies": { + "node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", + "dev": true, + "optional": true + } + } + }, "keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -46764,6 +51667,12 @@ "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, "kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -47156,6 +52065,15 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "linkify-it": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.1.tgz", + "integrity": "sha512-wVoTjP4Q6R0NW5hiZkVJaFZPWgtXfoGF+6LucL3/FtiNjmcHhYjEr5f1Kqjirc1nBW07J/ZuRFumqr2oqccEWg==", + "dev": true, + "requires": { + "uc.micro": "^2.0.0" + } + }, "lint-staged": { "version": "16.2.3", "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.3.tgz", @@ -47300,11 +52218,41 @@ "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", "dev": true }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "dev": true + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "dev": true + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "dev": true + }, "lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -47316,6 +52264,12 @@ "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true + }, "lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", @@ -47478,6 +52432,12 @@ "get-func-name": "^2.0.0" } }, + "lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "dev": true + }, "lru-cache": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", @@ -47488,6 +52448,23 @@ "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.3.tgz", "integrity": "sha512-Lkk/vx6ak3rYkRR0Nhu4lFUT2VDnQSxBe8Hbl7f36358p6ow8Bnvr8lrLt98H8J1aGxfhbX4Fs5tYg2+FTwr5Q==" }, + "macos-version": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/macos-version/-/macos-version-6.0.0.tgz", + "integrity": "sha512-O2S8voA+pMfCHhBn/TIYDXzJ1qNHpPDU32oFxglKnVdJABiYYITt45oLkV9yhwA3E2FDwn3tQqUFrTsr1p3sBQ==", + "dev": true, + "requires": { + "semver": "^7.3.5" + }, + "dependencies": { + "semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true + } + } + }, "magic-string": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.1.tgz", @@ -47574,6 +52551,34 @@ "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", "dev": true }, + "markdown-it": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.2.0.tgz", + "integrity": "sha512-1TGiQiJVRQ3NPmZH6sx5Cfnmg6GQm9jvC1ch4TK511NjSJvjzKLzn5pPfZRNZkRPZP0HqCioSndqH8v2nRaWVQ==", + "dev": true, + "requires": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.1", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "dependencies": { + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true + }, + "mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true + } + } + }, "markdown-to-jsx": { "version": "7.7.17", "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.7.17.tgz", @@ -47742,8 +52747,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "optional": true + "dev": true }, "mime-db": { "version": "1.52.0", @@ -47799,9 +52803,9 @@ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" }, "minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==" + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==" }, "minipass-collect": { "version": "1.0.2", @@ -48185,6 +53189,12 @@ "integrity": "sha512-32GSKM3Wyc8dg/p39lWPKYu8zci9mJFzV1Np9Of0ZEpe6Fhssn/FbI7ywAMd40uX+p3ZKh3T5EeCFv81qS3HmQ==", "dev": true }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, "mysql2": { "version": "3.15.3", "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz", @@ -48543,6 +53553,45 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz", "integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==" }, + "node-sarif-builder": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/node-sarif-builder/-/node-sarif-builder-3.4.0.tgz", + "integrity": "sha512-tGnJW6OKRii9u/b2WiUViTJS+h7Apxx17qsMUjsUeNDiMMX5ZFf8F8Fcz7PAQ6omvOxHZtvDTmOYKJQwmfpjeg==", + "dev": true, + "requires": { + "@types/sarif": "^2.1.7", + "fs-extra": "^11.1.1" + }, + "dependencies": { + "fs-extra": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz", + "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true + } + } + }, "node-ssh-forward": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/node-ssh-forward/-/node-ssh-forward-0.6.3.tgz", @@ -48659,6 +53708,12 @@ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", "dev": true }, + "normalize-url": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.1.tgz", + "integrity": "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==", + "dev": true + }, "nouislider": { "version": "15.4.0", "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-15.4.0.tgz", @@ -49141,6 +54196,12 @@ "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", "dev": true }, + "p-cancelable": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-4.0.1.tgz", + "integrity": "sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg==", + "dev": true + }, "p-each-series": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", @@ -49252,6 +54313,12 @@ "lines-and-columns": "^1.1.6" } }, + "parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true + }, "parse-node-version": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", @@ -49264,6 +54331,15 @@ "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", "dev": true }, + "parse-semver": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/parse-semver/-/parse-semver-1.1.1.tgz", + "integrity": "sha512-Eg1OuNntBMH0ojvEKSrvDSnwLmvVuUOSdylH/pSCPNMIspLlweJyIWXCE+k/5hm3cj/EBUYwmWkjhBALNP4LXQ==", + "dev": true, + "requires": { + "semver": "^5.1.0" + } + }, "parse-srcset": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", @@ -49274,6 +54350,59 @@ "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" }, + "parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "dev": true, + "requires": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "dependencies": { + "entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true + }, + "parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "requires": { + "entities": "^6.0.0" + } + } + } + }, + "parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "dev": true, + "requires": { + "parse5": "^7.0.0" + }, + "dependencies": { + "entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true + }, + "parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "requires": { + "entities": "^6.0.0" + } + } + } + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -49419,6 +54548,12 @@ } } }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -49626,6 +54761,12 @@ } } }, + "pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true + }, "png-js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/png-js/-/png-js-1.0.0.tgz", @@ -49741,6 +54882,12 @@ "integrity": "sha512-vPvPe8TKgp4FLgY3+DfxCE5PIfoXBK2lyLfNCxsRbDsV6vS4oU5RG/IWxrblMn6heagbnMED3MemUQllQ2bQUg==", "dev": true }, + "powershell-utils": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.2.0.tgz", + "integrity": "sha512-ZlsFlG7MtSFCoc5xreOvBAozCJ6Pf06opgJjh9ONEv418xpZSAzNjstD36C6+JwOnfSqOW/9uDkqKjezTdxZhw==", + "dev": true + }, "prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", @@ -49878,6 +55025,15 @@ } } }, + "pretty-ms": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "dev": true, + "requires": { + "parse-ms": "^4.0.0" + } + }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -50026,6 +55182,12 @@ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", "dev": true }, + "punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true + }, "pure-color": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/pure-color/-/pure-color-1.3.0.tgz", @@ -50057,6 +55219,12 @@ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true + }, "quote-stream": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/quote-stream/-/quote-stream-1.0.2.tgz", @@ -50138,6 +55306,18 @@ } } }, + "rc-config-loader": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/rc-config-loader/-/rc-config-loader-4.1.4.tgz", + "integrity": "sha512-3GiwEzklkbXTDp52UR5nT8iXgYAx1V9ZG/kDZT7p60u2GCv2XTwQq4NzinMoMpNtXhmt3WkhYXcj6HH8HdwCEQ==", + "dev": true, + "requires": { + "debug": "^4.4.3", + "js-yaml": "^4.1.1", + "json5": "2.2.3", + "require-from-string": "^2.0.2" + } + }, "re-resizable": { "version": "6.5.5", "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.5.5.tgz", @@ -50335,6 +55515,15 @@ } } }, + "read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", + "dev": true, + "requires": { + "mute-stream": "~0.0.4" + } + }, "read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -50549,6 +55738,12 @@ "supports-preserve-symlinks-flag": "^1.0.0" } }, + "resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true + }, "resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -50634,6 +55829,15 @@ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "dev": true }, + "responselike": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-4.0.2.tgz", + "integrity": "sha512-cGk8IbWEAnaCpdAt1BHzJ3Ahz5ewDJa0KseTsE3qIRMJ3C698W8psM7byCeWVpd/Ha7FUYzuRVzXoKoM6nRUbA==", + "dev": true, + "requires": { + "lowercase-keys": "^3.0.0" + } + }, "restore-cursor": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", @@ -50873,6 +56077,55 @@ } } }, + "run-jxa": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-jxa/-/run-jxa-3.0.0.tgz", + "integrity": "sha512-4f2CrY7H+sXkKXJn/cE6qRA3z+NMVO7zvlZ/nUV0e62yWftpiLAfw5eV9ZdomzWd2TXWwEIiGjAT57+lWIzzvA==", + "dev": true, + "requires": { + "execa": "^5.1.1", + "macos-version": "^6.0.0", + "subsume": "^4.0.0", + "type-fest": "^2.0.0" + }, + "dependencies": { + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true + } + } + }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -51092,8 +56345,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true, - "optional": true + "dev": true }, "saxes": { "version": "5.0.1", @@ -51135,6 +56387,146 @@ "temp": "^0.9.4" } }, + "secretlint": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/secretlint/-/secretlint-10.2.2.tgz", + "integrity": "sha512-xVpkeHV/aoWe4vP4TansF622nBEImzCY73y/0042DuJ29iKIaqgoJ8fGxre3rVSHHbxar4FdJobmTnLp9AU0eg==", + "dev": true, + "requires": { + "@secretlint/config-creator": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/node": "^10.2.2", + "@secretlint/profiler": "^10.2.2", + "debug": "^4.4.1", + "globby": "^14.1.0", + "read-pkg": "^9.0.1" + }, + "dependencies": { + "@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true + }, + "globby": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", + "dev": true, + "requires": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + } + }, + "hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "requires": { + "lru-cache": "^10.0.1" + } + }, + "ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true + }, + "normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "dev": true, + "requires": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + } + }, + "parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + } + }, + "path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "dev": true + }, + "read-pkg": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", + "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.3", + "normalize-package-data": "^6.0.0", + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" + }, + "dependencies": { + "unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true + } + } + }, + "semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true + }, + "slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true + }, + "type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true + } + } + }, + "selenium-webdriver": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.45.0.tgz", + "integrity": "sha512-Cb2nqvJiwXVOtRTCYHX9D1FJR5+Ls7aL3Nev0t6n4CpXsQ//YGiiUmSCbvTDDeLtbV85SZ46qmLab4SIYKXWRw==", + "dev": true, + "requires": { + "@bazel/runfiles": "^6.5.0", + "jszip": "^3.10.1", + "tmp": "^0.2.7", + "ws": "^8.21.0" + }, + "dependencies": { + "ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "dev": true, + "requires": {} + } + } + }, "semver": { "version": "5.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", @@ -51266,6 +56658,15 @@ "to-buffer": "^1.2.0" } }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + }, "shallow-copy": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", @@ -52009,6 +57410,15 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "structured-source": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/structured-source/-/structured-source-4.0.0.tgz", + "integrity": "sha512-qGzRFNJDjFieQkl/sVOI2dUjHKRyL9dAJi2gCPGJLbJHBIkyOHxjuocpIEfbLioX+qSJpvbYdT49/YCdMznKxA==", + "dev": true, + "requires": { + "boundary": "^2.0.0" + } + }, "stubs": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", @@ -52047,6 +57457,24 @@ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==" }, + "subsume": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/subsume/-/subsume-4.0.0.tgz", + "integrity": "sha512-BWnYJElmHbYZ/zKevy+TG+SsyoFCmRPDHJbR1MzLxkPOv1Jp/4hGhVUtP98s+wZBsBsHwCXvPTP0x287/WMjGg==", + "dev": true, + "requires": { + "escape-string-regexp": "^5.0.0", + "unique-string": "^3.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true + } + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -52127,6 +57555,12 @@ "tslib": "^2.5.0" } }, + "system-architecture": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/system-architecture/-/system-architecture-0.1.0.tgz", + "integrity": "sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==", + "dev": true + }, "tabbable": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.3.3.tgz", @@ -52277,6 +57711,91 @@ "streamx": "^2.15.0" } }, + "targz": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/targz/-/targz-1.0.1.tgz", + "integrity": "sha512-6q4tP9U55mZnRuMTBqnqc3nwYQY3kv+QthCFZuMk+Tn1qYUnMPmL/JZ/mzgXINzFpSqfU+242IFmFU9VPvqaQw==", + "dev": true, + "requires": { + "tar-fs": "^1.8.1" + }, + "dependencies": { + "bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "dev": true, + "requires": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "pump": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", + "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "1.3.0", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "tar-fs": { + "version": "1.16.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.6.tgz", + "integrity": "sha512-JkOgFt3FxM/2v2CNpAVHqMW2QASjc/Hxo7IGfNd3MHaDYSW/sBFiS7YVmmhmr8x6vwN1VFQDQGdT2MWpmIuVKA==", + "dev": true, + "requires": { + "chownr": "^1.0.1", + "mkdirp": "^0.5.1", + "pump": "^1.0.0", + "tar-stream": "^1.1.2" + } + }, + "tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "dev": true, + "requires": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + } + } + } + }, "tas-client": { "version": "0.2.33", "resolved": "https://registry.npmjs.org/tas-client/-/tas-client-0.2.33.tgz", @@ -52452,6 +57971,15 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "textextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-6.11.0.tgz", + "integrity": "sha512-tXJwSr9355kFJI3lbCkPpUH5cP8/M0GGy2xLO34aZCjMXBaK3SoPnZwr/oWmo1FdCnELcs4npdCIOFtq9W3ruQ==", + "dev": true, + "requires": { + "editions": "^6.21.0" + } + }, "throat": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", @@ -52955,6 +58483,17 @@ "is-typed-array": "^1.1.9" } }, + "typed-rest-client": { + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.11.tgz", + "integrity": "sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==", + "dev": true, + "requires": { + "qs": "6.15.2", + "tunnel": "0.0.6", + "underscore": "^1.12.1" + } + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -53001,6 +58540,12 @@ } } }, + "uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true + }, "ufo": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.2.tgz", @@ -53110,6 +58655,12 @@ } } }, + "unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true + }, "unique-filename": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", @@ -53128,6 +58679,15 @@ "imurmurhash": "^0.1.4" } }, + "unique-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", + "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", + "dev": true, + "requires": { + "crypto-random-string": "^4.0.0" + } + }, "universal-user-agent": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", @@ -53150,6 +58710,48 @@ "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", "dev": true }, + "unzipper": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.12.5.tgz", + "integrity": "sha512-tXYOi9R57Uj/2Z25SOs5RRSzq886MBQj2gY8dPL+xl/kv6s6SvByoKfAtvfVeEuhntWDgjd2o9p2lb4TVPAz0A==", + "dev": true, + "requires": { + "bluebird": "~3.7.2", + "duplexer2": "~0.1.4", + "fs-extra": "11.3.1", + "graceful-fs": "^4.2.2", + "node-int64": "^0.4.0" + }, + "dependencies": { + "fs-extra": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz", + "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true + } + } + }, "update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -53184,6 +58786,12 @@ "qs": "^6.12.3" } }, + "url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true + }, "url-parse": { "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", @@ -53989,6 +59597,12 @@ } } }, + "version-range": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.15.0.tgz", + "integrity": "sha512-Ck0EJbAGxHwprkzFO966t4/5QkRuzh+/I1RxhLgUKKwEn+Cd8NwM60mE3AqBZg5gYODoXW0EFsQvbZjRlvdqbg==", + "dev": true + }, "vinyl-contents": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/vinyl-contents/-/vinyl-contents-2.0.0.tgz", @@ -54037,6 +59651,226 @@ "resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.51.0.tgz", "integrity": "sha512-dzKWTMMyebIMPF1VYMuuQj7gGFq7guR8AFya0mKacu+ayptJfaRuM0mdHCqiOth4FnRP8mPhEroFPx6Ift8wHA==" }, + "vscode-extension-tester": { + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/vscode-extension-tester/-/vscode-extension-tester-8.23.0.tgz", + "integrity": "sha512-YElhRDkOjvmGFv44aCMnEnUM5RmFqWByQ8TBr7/6N9K/b5o1gu2fo9EgTef6VmrB4kyFnSrkPVAbiZBfKQA1mQ==", + "dev": true, + "requires": { + "@redhat-developer/locators": "^1.20.0", + "@redhat-developer/page-objects": "^1.20.0", + "@types/selenium-webdriver": "^4.35.5", + "@vscode/vsce": "^3.7.1", + "c8": "^11.0.0", + "commander": "^14.0.3", + "compare-versions": "^6.1.1", + "find-up": "8.0.0", + "fs-extra": "^11.3.4", + "glob": "^13.0.6", + "got": "^14.6.6", + "hpagent": "^1.2.0", + "js-yaml": "^4.1.1", + "sanitize-filename": "^1.6.3", + "selenium-webdriver": "^4.41.0", + "targz": "^1.0.1", + "unzipper": "^0.12.3" + }, + "dependencies": { + "@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true + }, + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true + }, + "brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "requires": { + "balanced-match": "^4.0.2" + } + }, + "c8": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/c8/-/c8-11.0.0.tgz", + "integrity": "sha512-e/uRViGHSVIJv7zsaDKM7VRn2390TgHXqUSvYwPHBQaU6L7E9L0n9JbdkwdYPvshDT0KymBmmlwSpms3yBaMNg==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^1.0.1", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^8.0.0", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "dependencies": { + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + } + } + }, + "commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true + }, + "find-up": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-8.0.0.tgz", + "integrity": "sha512-JGG8pvDi2C+JxidYdIwQDyS/CgcrIdh18cvgxcBge3wSHRQOrooMD3GlFBcmMJAN9M42SAZjDp5zv1dglJjwww==", + "dev": true, + "requires": { + "locate-path": "^8.0.0", + "unicorn-magic": "^0.3.0" + }, + "dependencies": { + "locate-path": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-8.0.0.tgz", + "integrity": "sha512-XT9ewWAC43tiAV7xDAPflMkG0qOPn2QjHqlgX8FOqmWa/rxnyYDulF9T0F7tRy1u+TVTmK/M//6VIOye+2zDXg==", + "dev": true, + "requires": { + "p-locate": "^6.0.0" + } + } + } + }, + "foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + } + }, + "fs-extra": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz", + "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "requires": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + } + }, + "jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "dev": true + }, + "minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "requires": { + "brace-expansion": "^5.0.5" + } + }, + "p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "requires": { + "yocto-queue": "^1.0.0" + } + }, + "p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "requires": { + "p-limit": "^4.0.0" + } + }, + "path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "requires": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + } + }, + "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 + }, + "test-exclude": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-8.0.0.tgz", + "integrity": "sha512-ZOffsNrXYggvU1mDGHk54I96r26P8SyMjO5slMKSc7+IWmtB/MQKnEC2fP51imB3/pT6YK5cT5E8f+Dd9KdyOQ==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^13.0.6", + "minimatch": "^10.2.2" + } + }, + "universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true + }, + "yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "dev": true + } + } + }, "vscode-jsonrpc": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", @@ -54419,6 +60253,26 @@ "async-limiter": "~1.0.0" } }, + "wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "requires": { + "is-wsl": "^3.1.0" + }, + "dependencies": { + "is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "requires": { + "is-inside-container": "^1.0.0" + } + } + } + }, "xdg-basedir": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", @@ -54436,6 +60290,22 @@ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" }, + "xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "dev": true, + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true + }, "xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", @@ -54531,6 +60401,24 @@ } } }, + "yauzl": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.4.0.tgz", + "integrity": "sha512-jIH9yLR9wqr0wOS0TpBvo/g/2UgZH5qePVbjgRliiF0BYvOZyaBknKsF+x9Iht0O6sqgnB93rCICdOZFecJuDw==", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, + "yazl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", + "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3" + } + }, "yjs": { "version": "13.6.29", "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.29.tgz", diff --git a/package.json b/package.json index 06fc638006..dbdd0573ec 100644 --- a/package.json +++ b/package.json @@ -2672,6 +2672,13 @@ "test:performance:execution": "cross-env VSC_JUPYTER_CI_TEST_GREP=@executionPerformance CODE_TESTS_WORKSPACE=src/test/datascience TEST_FILES_SUFFIX=*.vscode.test,*.vscode.common.test VSC_JUPYTER_FORCE_LOGGING= node ./out/test/standardTest.node.js", "test:performance:notebook": "cross-env VSC_JUPYTER_CI_TEST_GREP=@notebookPerformance VSC_JUPYTER_CI_TEST_DO_NOT_INSTALL_PYTHON_EXT=true CODE_TESTS_WORKSPACE=src/test/datascience TEST_FILES_SUFFIX=*.vscode.test,*.vscode.common.test node ./out/test/standardTest.node.js", "test:smoke": "cross-env VSC_JUPYTER_FORCE_LOGGING=true node --no-force-async-hooks-checks ./out/test/smokeTest.node.js", + "compile-e2e": "tsc -p ./test/e2e/tsconfig.json", + "pretest:e2e": "npm run compile-e2e", + "setup:e2e:vscode": "extest get-vscode -c max && extest get-chromedriver -c max", + "setup:e2e:deps": "extest install-from-marketplace ms-python.python -e .test-extensions", + "setup:e2e:proposed-api": "node ./test/e2e/enable-proposed-api.js", + "setup:e2e": "npm run setup:e2e:vscode && npm run setup:e2e:deps && npm run setup:e2e:proposed-api", + "test:e2e": "extest setup-and-run \"./out/e2e/suite/*.e2e.test.js\" -c max -o ./test/e2e/settings.json -e .test-extensions -m ./test/e2e/.mocharc.js -i", "test:unittests": "mocha --config ./build/.mocha.unittests.js.json ./out/**/*.unit.test.js", "test": "npm run test:unittests", "typecheck": "tsc -p ./ --noEmit", @@ -2910,7 +2917,8 @@ "typescript": "^5.8.3", "unicode-properties": "^1.3.1", "utf-8-validate": "^5.0.8", - "util": "^0.12.4" + "util": "^0.12.4", + "vscode-extension-tester": "^8.23.0" }, "lint-staged": { "src/**/*.{ts,tsx}": [ diff --git a/specs/e2e-extester-testing-plan.md b/specs/e2e-extester-testing-plan.md new file mode 100644 index 0000000000..650da02965 --- /dev/null +++ b/specs/e2e-extester-testing-plan.md @@ -0,0 +1,1250 @@ +# E2E Testing with ExTester (vscode-extension-tester) — Self-Contained Plan & Reference + +> **What this document is.** A single, self-contained guide to the end-to-end (E2E) UI +> test layer for this extension, built on Red Hat's **ExTester** +> (`vscode-extension-tester`). It explains *why* and *how*, and embeds the **complete, +> verbatim contents of every file** involved, so you can understand, reproduce, run, and +> extend the setup from this document alone — without opening any other file. +> +> **Status:** implemented **and verified passing locally** on headless Linux (Ubuntu 24.04, +> Xvfb, VS Code 1.111.0). The files below exist in the repo at the stated paths. +> +> **The one test we ship** drives the full Deepnote happy path through the *real* VS Code +> UI: open a one-notebook `.deepnote` file containing `print("hello world")` → create a +> Deepnote environment → select it for the notebook → kernel connects → run the cell → +> assert the rendered output contains `hello world`. + +--- + +## 0. Implementation reality (read this first) + +Bringing this test green required several fixes beyond the first draft of the plan. They are +baked into the files reproduced below; this section explains the *why* so the test reads +sensibly and so the setup is reproducible. + +**Setup the headless run needs (one-time):** + +- **System libraries for Electron/Chromium** (the test VS Code won't launch without them). On + Ubuntu 24.04: `libatk1.0-0t64 libatk-bridge2.0-0t64 libcups2t64 libgtk-3-0t64 + libgdk-pixbuf-2.0-0 libgbm1 libasound2t64 libnss3 libnspr4 libxss1 libxshmfence1 libdrm2 + libxkbcommon0 libxcomposite1 libxdamage1 libxrandr2 libxfixes3 libxext6 libxrender1 + libpango-1.0-0 libcairo2 libatspi2.0-0 libx11-xcb1 libxcb-dri3-0 libxtst6 libsecret-1-0 + libgssapi-krb5-2 libdbus-1-3 libexpat1` plus `xvfb`. +- **A venv-capable Python interpreter** — env creation runs `python -m venv` (needs + `ensurepip`) then `pip install deepnote-toolkit[server] ipykernel python-lsp-server + deepnote-cli` (needs network). On Ubuntu: `python3.12-venv python3-pip`. +- **Proposed-API allow-listing.** The extension declares `enabledApiProposals` (notebook + kernel/execution APIs). VS Code blocks those for a normally-installed VSIX, and ExTester's + `setup-and-run` exposes no `--enable-proposed-api` flag, so we write the extension into the + downloaded VS Code's `product.json` `extensionEnabledApiProposals` allow-list — the same + mechanism stable VS Code uses for Microsoft extensions. Done by `test/e2e/enable-proposed-api.js` + (§6.8), wired as `npm run setup:e2e:proposed-api`. **Without this the extension never + activates** and the notebook stays empty. +- **`.gitignore` is not `.vscodeignore`.** `vsce` packs from `.vscodeignore`; the e2e + `.test-extensions/` dir (~200 MB) must be excluded there too or every run packages a + ~300 MB VSIX. (§6.2) + +**Why the test code looks the way it does (vs a naive script):** + +- **Open a workspace *folder*, not just the file.** The Deepnote serializer reads a "snapshot" + during deserialization and, with **no** workspace folder, blocks forever on + `await window.showWarningMessage('Cannot read snapshot: No workspace folders found.')` — + leaving the notebook blank. So the test opens the temp dir as a folder first, then the file. +- **ExTester's `openResources` (`code -r `) silently no-ops** in this sandboxed instance + (IPC reuse fails), so we drive the *running* window directly: the folder via the simple + "Open Folder" dialog, the notebook via Quick Open ("Go to File…"). +- **The simple "Open Folder" dialog's Enter navigates *into* a directory** rather than + accepting it — the deterministic accept is the dialog's **"OK" button**, which we click. The + open also reloads the window, so we wait for the old workbench element to detach. +- **`deepnote.runallcells` is gated** behind context keys (`deepnote.ispythonornativeactive`, + …) that aren't reliably set under automation, so `Workbench.executeCommand('Jupyter: Run All + Cells')` can miss and open the wrong view. We **click the notebook toolbar's "Run All" + button** instead, and **re-issue it periodically** because the first run can be dropped right + after the kernel connects. +- **Environment creation is idempotent** (treats "already exists" as success) with a stable + name, so retries — and persistent local instances — reuse the already-provisioned venv. + +--- + +## Table of contents + +0. [Implementation reality (read this first)](#0-implementation-reality-read-this-first) +1. [TL;DR — run it](#1-tldr--run-it) +2. [Background: what ExTester is and how it works](#2-background-what-extester-is-and-how-it-works) +3. [The Deepnote flow this test drives (verified against the code)](#3-the-deepnote-flow-this-test-drives-verified-against-the-code) +4. [Design decisions](#4-design-decisions) +5. [File manifest](#5-file-manifest) +6. [Complete file contents (verbatim)](#6-complete-file-contents-verbatim) + - 6.1 [`package.json` additions](#61-packagejson-additions) + - 6.2 [`.gitignore` additions](#62-gitignore-additions) + - 6.3 [`test/e2e/tsconfig.json`](#63-teste2etsconfigjson) + - 6.4 [`test/e2e/.mocharc.js`](#64-teste2emocharcjs) + - 6.5 [`test/e2e/settings.json`](#65-teste2esettingsjson) + - 6.6 [`test/e2e/fixtures/hello-world.deepnote`](#66-teste2efixtureshello-worlddeepnote) + - 6.7 [`test/e2e/suite/helloWorld.e2e.test.ts`](#67-teste2esuitehelloworlde2etestts) + - 6.8 [`test/e2e/enable-proposed-api.js`](#68-teste2eenable-proposed-apijs) +7. [How the hard parts work](#7-how-the-hard-parts-work) + - 7.1 [Reading rendered output from nested iframes](#71-reading-rendered-output-from-nested-iframes) + - 7.2 [Driving QuickPicks & InputBoxes](#72-driving-quickpicks--inputboxes) +8. [Running it](#8-running-it) +9. [CI integration](#9-ci-integration) +10. [Gotchas & flakiness mitigation](#10-gotchas--flakiness-mitigation) +11. [Where ExTester fits vs the other test layers](#11-where-extester-fits-vs-the-other-test-layers) +12. [Risks & mitigations](#12-risks--mitigations) +13. [Appendix A — ExTester API surface used](#appendix-a--extester-api-surface-used) +14. [Appendix B — Deepnote command-id reference](#appendix-b--deepnote-command-id-reference) +15. [References](#references) + +--- + +## 1. TL;DR — run it + +```bash +# one-time / when the extension changes +npm run compile # build the extension under test → dist/extension.node.js +npm run setup:e2e # download test VS Code + ChromeDriver, install ms-python.python, + # and allow-list the extension's proposed APIs in product.json + +# run the E2E suite (pretest compiles test/e2e → out/e2e, then extest packages & runs) +npm run test:e2e + +# headless Linux (CI or a server without a display): wrap in a virtual framebuffer +xvfb-run --auto-servernum --server-args='-screen 0 1920x1080x24' npm run test:e2e +``` + +`npm run setup:e2e` is the umbrella for `setup:e2e:vscode` → `setup:e2e:deps` → +`setup:e2e:proposed-api` (in that order — the proposed-API patch needs VS Code already +downloaded). Re-running it is safe (idempotent). + +Prerequisites: the **system libraries + Xvfb** and a **venv-capable Python interpreter** from +§0, and **network access** (creating the environment installs the Deepnote toolkit; the first +kernel start can take minutes). + +--- + +## 2. Background: what ExTester is and how it works + +**ExTester** (`vscode-extension-tester`, current `^8.23.0`) is a UI-testing framework for +VS Code extensions built on **Selenium WebDriver**. It drives the *real* VS Code desktop +app (Electron/Chromium) as if it were a browser — clicking buttons, opening the command +palette, typing into editors, reading notifications, and inspecting the DOM — exercising +the extension exactly as a user would. Red Hat created it to UI-test their own extensions +(vscode-java, vscode-quarkus, vscode-server-connector, …) and it is the de-facto standard +for VS Code extension UI testing. + +The `extest` CLI does five things: + +1. Downloads a clean, pinned **VS Code** test instance. +2. Downloads the **ChromeDriver** matching that VS Code's Chromium. +3. Loads our extension into it (from source by default; from a `.vsix` with `-u`). +4. Launches VS Code under WebDriver and runs our **Mocha** test files. +5. Auto-screenshots failing tests into `/_screenshots`. + +``` +Mocha test → ExTester Page Objects → selenium-webdriver → ChromeDriver → VS Code (+ our extension) +``` + +We write tests against ExTester's **Page Object API** (`Workbench`, `EditorView`, +`InputBox`, `Notification`, `WebView`, …) — all imported from the single +`vscode-extension-tester` package — instead of hand-writing DOM selectors. + +**Key facts that shape this plan** (from the ExTester docs + studying Red Hat's own repos): + +- **No headless flag.** VS Code is a real GUI app; on Linux you run "headed" inside + `xvfb`. (§8, §9) +- **WebViews are iframes.** Anything rendered in a webview (including **notebook cell + output**) requires switching the WebDriver context into the iframe and back. (§7.1) +- **No notebook page object exists.** ExTester has `TextEditor`, `WebView`, + `CustomEditor`, … but nothing notebook-specific. We use `WebView` — its frame-switching + happens to descend exactly the two iframe levels the notebook output uses. (§7.1) +- **chai v4 + CommonJS.** Every current Red Hat repo uses chai v4 with CommonJS; chai v5 + is ESM-only and adds friction. This repo already ships chai `^4.3.10`. (§4) +- **Compatibility is a floating window.** ExTester officially supports the latest ~3 VS + Code minors (`-c min|max`), oldest workable is 1.37.0; use the newest ExTester for the + newest VS Code. Node = active LTS. (§4) +- **Reliability comes from `driver.wait(...)`,** preferring `Workbench.executeCommand` + over clicking menus, always `switchBack()` in `finally`, and bumping Mocha timeouts well + above the unusable 2 s default. (§10) + +--- + +## 3. The Deepnote flow this test drives (verified against the code) + +Each step below was confirmed by reading the extension source (anchors given for +maintainers). + +1. **Open `.deepnote` → native Notebook editor.** The extension registers a serializer for + notebook type **`deepnote`** (`package.json` → `contributes.notebooks[].type = "deepnote"`, + selector `*.deepnote`; `DeepnoteNotebookSerializer` in + `src/notebooks/deepnote/deepnoteSerializer.ts`; registered in + `deepnoteActivationService.ts`). Opening the raw file works without the explorer: a + single-notebook file resolves via `findDefaultNotebook()` (serializer line ~103). + Before an environment is chosen, the notebook gets a **placeholder controller** labeled + **"Deepnote: Select Environment"** (`deepnoteKernelAutoSelector.node.ts` → + `createPlaceholderController`). + +2. **Create an environment** → command **`deepnote.environments.create`** (palette label + **"Deepnote: Create Environment"**; `deepnoteEnvironmentsView.node.ts` → + `createEnvironmentCommand`). It prompts, in order: + - a **QuickPick of Python interpreters** (from the Python extension API + `api.environments.known`; placeholder *"Select a Python interpreter for this + environment"*) — if none are discovered yet it shows *"No Python interpreters found"* + and returns; + - an **input box for the name**; + - an **input box for packages** (optional — empty + Enter is valid); + - an **input box for description** (optional); + - then a progress notification *"Creating environment …"* and finally + *"Environment "…" created successfully!"*. + +3. **Select it for the notebook** → command **`deepnote.environments.selectForNotebook`** + (palette label **"Deepnote: Select Environment for Notebook"**; requires the active + editor to be a `deepnote` notebook). It shows a **QuickPick of environments** (plus + *"$(add) Create New Environment"*). Choosing one calls + `kernelAutoSelector.rebuildController(notebook, …)` inside a *"Switching to + environment…"* progress notification, ending with *"Environment switched + successfully"*. + +4. **Kernel connects.** `rebuildController` → `ensureKernelSelectedWithConfiguration` → + `ensureControllerSelectedForNotebook` provisions the venv/toolkit, **explicitly selects + the controller** via `commands.executeCommand('notebook.selectKernel', { … id: controller.connection.id … })` + (auto-selector line ~740), and **disposes the placeholder** (line ~502). After this the + real controller is the selected kernel, so "Run All" executes through it. + +5. **Execute** → command **`deepnote.runallcells`** (palette label **"Jupyter: Run All + Cells"**), which runs the cell through the selected controller, starting the kernel. + +6. **Validate output** → the rendered stdout `hello world` appears in the notebook + **output webview** (nested iframes — §7.1). + +### The fixture format + +Confirmed from `src/notebooks/deepnote/deepnoteSerializer.unit.test.ts` and `@deepnote/blocks`. +A minimal one-notebook, one-code-block file is in §6.6. + +--- + +## 4. Design decisions + +| Decision | Choice | Why | +| --- | --- | --- | +| Where tests live | `test/e2e/` (top-level, **outside `src/`**) | The root `tsconfig.json` only compiles `./src/**/*` and esbuild only bundles `src`, so E2E code never enters the extension bundle, the unit glob (`out/**/*.unit.test.js`), or the integration glob (`*.vscode.test`). Zero interference. | +| Compilation | Dedicated `test/e2e/tsconfig.json` → `out/e2e/` | Isolated from the strict extension config; `out/` is already gitignored, so compiled tests and `_screenshots` are ignored too. | +| Module system | **CommonJS + chai v4** | Matches every current Red Hat repo and the repo's existing chai `^4.3.10`; avoids chai v5 ESM friction. | +| New dependency | only `vscode-extension-tester@^8.23.0` | It transitively brings selenium-webdriver, page-objects, locators, @vscode/vsce, c8. `mocha` (`^11`), `chai`/`@types/chai` (`^4`), `@types/mocha` (`^10`) already exist and are reused. | +| VS Code version | `-c max` | Best DOM-locator match for ExTester 8.23 and most reliable automation; still within our `engines.vscode` `^1.95.0`. Swap to `-c 1.95.0` to additionally validate the minimum supported VS Code. | +| Output validation | read the **output iframe**, gated on `getViewToSwitchTo()` | Reading the whole document body would falsely match the cell's *source* `print("hello world")` shown in the editor. Gating on a real output iframe + output-scoped selectors prevents that. (§7.1) | +| Fixture handling | copy to a temp dir, open the copy | Execution dirties the notebook; a throwaway copy keeps the committed fixture pristine and avoids save prompts. | + +--- + +## 5. File manifest + +| Path | New? | Purpose | +| --- | --- | --- | +| `package.json` | modified | adds `vscode-extension-tester` devDep + the e2e npm scripts | +| `.gitignore` | modified | ignores ExTester's `test-resources/` and `.test-extensions/` | +| `.vscodeignore` | modified | excludes `.test-extensions/` + `test-resources/` from the VSIX (§6.2) | +| `test/e2e/tsconfig.json` | new | isolated CommonJS compile → `out/e2e` | +| `test/e2e/.mocharc.js` | new | UI-test timeouts/retries/reporter | +| `test/e2e/settings.json` | new | VS Code user settings for the test instance | +| `test/e2e/fixtures/hello-world.deepnote` | new | the one-notebook hello-world fixture | +| `test/e2e/enable-proposed-api.js` | new | allow-lists proposed APIs in the test VS Code's `product.json` (§0, §6.8) | +| `test/e2e/suite/helloWorld.e2e.test.ts` | new | the single E2E test | + +Resulting layout: + +``` +test/e2e/ +├── tsconfig.json +├── .mocharc.js +├── settings.json +├── fixtures/ +│ └── hello-world.deepnote +└── suite/ + └── helloWorld.e2e.test.ts +``` + +ExTester writes its downloads to `test-resources/` and installs extensions into +`.test-extensions/` (both gitignored). Compiled tests + failure screenshots live under +`out/e2e/` (already gitignored via `out`). + +--- + +## 6. Complete file contents (verbatim) + +### 6.1 `package.json` additions + +Add one dev dependency: + +```jsonc +"devDependencies": { + // …existing… + "vscode-extension-tester": "^8.23.0" +} +``` + +Add these scripts (placed alongside the other `test:*` scripts): + +```jsonc +"scripts": { + // …existing… + "compile-e2e": "tsc -p ./test/e2e/tsconfig.json", + "pretest:e2e": "npm run compile-e2e", + "setup:e2e:vscode": "extest get-vscode -c max && extest get-chromedriver -c max", + "setup:e2e:deps": "extest install-from-marketplace ms-python.python -e .test-extensions", + "setup:e2e:proposed-api": "node ./test/e2e/enable-proposed-api.js", + "setup:e2e": "npm run setup:e2e:vscode && npm run setup:e2e:deps && npm run setup:e2e:proposed-api", + "test:e2e": "extest setup-and-run \"./out/e2e/suite/*.e2e.test.js\" -c max -o ./test/e2e/settings.json -e .test-extensions -m ./test/e2e/.mocharc.js -i" +} +``` + +`setup:e2e:deps` installs the Python extension using the *downloaded* test VS Code's CLI, so +`setup:e2e:vscode` must run first; `setup:e2e:proposed-api` patches that VS Code's +`product.json`, which `setup-and-run` then reuses from cache (it does not re-extract a cached +VS Code, so the patch survives). + +What the `extest setup-and-run` flags mean: + +- positional glob `"./out/e2e/suite/*.e2e.test.js"` — the compiled test(s) to run. +- `-c max` — download the newest VS Code ExTester supports (matching ChromeDriver fetched + automatically). +- `-o ./test/e2e/settings.json` — user settings applied to the test instance. +- `-e .test-extensions` — isolated extensions directory. +- `-m ./test/e2e/.mocharc.js` — the Mocha config. +- `-i` — install declared extension *dependencies* from the Marketplace. (We have none + declared, so the Python extension is installed separately via `setup:e2e:deps`, since it + is not an `extensionDependency`.) + +### 6.2 `.gitignore` additions + +```gitignore +# ExTester (vscode-extension-tester) E2E artifacts +test-resources +.test-extensions +``` + +(`out/` is already ignored, covering `out/e2e/` and any `_screenshots` beneath it.) + +**Also add to `.vscodeignore`** (separate from `.gitignore` — `vsce` reads `.vscodeignore` +when it exists and ignores `.gitignore` entirely). `test/` and `out/` are already excluded +there, but the e2e *artifact* dirs are not, and `-e .test-extensions` puts ~200 MB / 10k files +in the repo root that would otherwise be packed into the VSIX on every run: + +```gitignore +.test-extensions/** +test-resources/** +``` + +### 6.3 `test/e2e/tsconfig.json` + +```jsonc +{ + // Standalone config for the ExTester E2E suite. It is intentionally independent of the + // extension's root tsconfig (which only compiles ./src) so these tests never enter the + // esbuild bundle or the unit/integration test globs. CommonJS + chai v4 keeps + // `import { expect } from 'chai'` working without ESM friction. + "compilerOptions": { + "module": "commonjs", + "target": "ES2022", + "lib": ["ES2022", "DOM"], + "moduleResolution": "node", + "outDir": "../../out/e2e", + "rootDir": ".", + "sourceMap": true, + "strict": true, + "skipLibCheck": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "types": ["node", "mocha", "chai"] + }, + "include": ["**/*.ts"] +} +``` + +### 6.4 `test/e2e/.mocharc.js` + +```js +// Mocha configuration for the ExTester (vscode-extension-tester) E2E suite. +// UI tests are slow: the 2s Mocha default is unusable. Individual waits inside the +// tests are the real guard rails; this is a generous suite-level safety net. +module.exports = { + timeout: 900000, // 15 min — env creation + first kernel start (venv + toolkit) can be slow + retries: 1, // absorb transient UI flakiness with a single retry + reporter: 'spec', + color: true +}; +``` + +### 6.5 `test/e2e/settings.json` + +Reduces UI noise and makes automation deterministic. The `files.simpleDialog.enable` + +`window.dialogStyle: custom` pair turns native OS dialogs (undriveable by Selenium) into +in-window quick inputs — a Red-Hat-wide best practice. `security.workspace.trust.enabled: +false` prevents a workspace-trust modal from blocking the run. + +```json +{ + "files.simpleDialog.enable": true, + "window.dialogStyle": "custom", + "workbench.editor.enablePreview": false, + "workbench.startupEditor": "none", + "extensions.ignoreRecommendations": true, + "workbench.remoteIndicator.showExtensionRecommendations": false, + "git.autoRepositoryDetection": false, + "telemetry.telemetryLevel": "off", + "update.mode": "none", + "jupyter.kernels.trusted": true, + "security.workspace.trust.enabled": false +} +``` + +### 6.6 `test/e2e/fixtures/hello-world.deepnote` + +```yaml +version: '1.0.0' +metadata: + createdAt: '2025-01-01T00:00:00.000Z' + modifiedAt: '2025-01-01T00:00:00.000Z' +project: + id: e2e-hello-world-project + name: E2E Hello World + notebooks: + - id: e2e-hello-world-notebook + name: Hello World + blocks: + - id: e2e-hello-block + blockGroup: e2e-group + type: code + content: |- + print("hello world") + sortingKey: a0 + metadata: {} + executionMode: block + isModule: false + settings: {} +``` + +### 6.7 `test/e2e/suite/helloWorld.e2e.test.ts` + +```ts +/** + * End-to-end UI test driven by ExTester (vscode-extension-tester). + * + * It exercises the full Deepnote happy path through the *real* VS Code UI: + * 1. open a one-notebook `.deepnote` file containing `print("hello world")` + * 2. create a Deepnote environment (command `deepnote.environments.create`) + * 3. select that environment for the notebook (command `deepnote.environments.selectForNotebook`) + * — this builds and selects the notebook's kernel controller ("kernel connected") + * 4. run the cell (the notebook toolbar's "Run All" button) + * 5. assert the rendered stdout output contains "hello world" + * + * Prerequisites (see specs/e2e-extester-testing-plan.md): + * - The Python extension (`ms-python.python`) must be installed in the test instance + * (`npm run setup:e2e:deps`) and at least one Python interpreter must be discoverable. + * - Creating the environment provisions a venv and the Deepnote toolkit, which needs + * network access; the first kernel start can take a few minutes. + * + * Notebook output in VS Code renders inside two nested iframes + * (iframe.webview.ready -> #active-frame). ExTester's WebView page object descends + * exactly those two levels, which is how we read the rendered output below. + */ + +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; + +import { expect } from 'chai'; +import { + By, + EditorView, + InputBox, + Notification, + VSBrowser, + WebView, + Workbench, + type WebDriver +} from 'vscode-extension-tester'; + +// Command palette labels (category + title) the way `Workbench.executeCommand` matches them. +const CREATE_ENV_COMMAND = 'Deepnote: Create Environment'; +const SELECT_ENV_COMMAND = 'Deepnote: Select Environment for Notebook'; + +const NOTEBOOK_FILE_NAME = 'hello-world.deepnote'; +const EXPECTED_OUTPUT = 'hello world'; + +// Timeouts (ms). UI ops are slow and the first kernel start is the slowest step. +const WORKBENCH_TIMEOUT = 60_000; +const QUICK_PICK_TIMEOUT = 30_000; +const ENV_CREATED_TIMEOUT = 120_000; +const KERNEL_CONNECT_TIMEOUT = 300_000; +const OUTPUT_TIMEOUT = 300_000; +// How often to re-issue "Run All" while waiting for output — the first run can be dropped right +// after the kernel connects. +const RUN_ALL_REISSUE_INTERVAL = 25_000; +const INTERPRETER_RETRY_DELAY = 5_000; +const MAX_CREATE_ATTEMPTS = 6; +// The in-window simple file/folder dialog needs a beat to resolve a typed path before it accepts. +const DIALOG_RESOLVE_DELAY = 1_500; +const FOLDER_OPEN_ATTEMPTS = 5; +const FOLDER_RELOAD_TIMEOUT = 12_000; + +// Selectors that only exist inside the notebook output iframe (`#active-frame`), +// so reading them cannot accidentally match the cell's source in the editor. +const OUTPUT_SELECTOR = '.output_container, .output, .rendered-output'; + +describe('Deepnote E2E — run "hello world"', function () { + // Per-test timeout for the whole suite (overrides the mocharc default for these tests). + this.timeout(22 * 60 * 1000); + + let driver: WebDriver; + let notebookFile: string; + // A stable name: createEnvironment is idempotent (it treats "already exists" as success), so a + // leftover environment from a previous or retried run is reused rather than colliding — which + // also lets a persistent test instance reuse the already-provisioned venv. + const environmentName = 'E2E Hello Env'; + + before(async function () { + driver = VSBrowser.instance.driver; + + // Open a throwaway copy so execution-dirtied notebook state never touches the source tree. + const source = path.resolve(process.cwd(), 'test', 'e2e', 'fixtures', NOTEBOOK_FILE_NAME); + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'deepnote-e2e-')); + notebookFile = path.join(tempDir, NOTEBOOK_FILE_NAME); + fs.copyFileSync(source, notebookFile); + + await VSBrowser.instance.waitForWorkbench(WORKBENCH_TIMEOUT); + + // Open the temp directory as a workspace folder FIRST. The Deepnote serializer reads a + // "snapshot" during deserialization and, with no workspace folder open, blocks on a + // `showWarningMessage('Cannot read snapshot: No workspace folders found.')` that never + // resolves headlessly — leaving the notebook blank. A workspace folder also provides the + // requirements.txt path the kernel auto-selector needs. (Opening a folder reloads the + // window, so we re-wait for the workbench afterwards.) + await openFolderViaDialog(tempDir); + await VSBrowser.instance.waitForWorkbench(WORKBENCH_TIMEOUT); + + // Open the notebook by driving the running window directly. ExTester's `openResources` + // shells out to `code -r ` (reuse-window over IPC), which silently no-ops in a + // sandboxed/headless environment. Now that the containing folder is the workspace, the + // notebook is reachable by name through Quick Open ("Go to File..."). + await openWorkspaceFile(NOTEBOOK_FILE_NAME); + + // The native notebook editor opens because the extension registers a serializer for + // the `deepnote` notebook type; a single-notebook file resolves to its default notebook. + await driver.wait( + async () => (await new EditorView().getOpenEditorTitles()).some((t) => t.includes(NOTEBOOK_FILE_NAME)), + WORKBENCH_TIMEOUT, + 'Deepnote notebook editor did not open' + ); + }); + + after(async function () { + // Defensive cleanup: never leave the driver stuck inside a webview frame, and close tabs. + await new WebView().switchBack().catch(() => undefined); + await new EditorView().closeAllEditors().catch(() => undefined); + }); + + it('creates an environment, connects the kernel, runs the cell and renders output', async function () { + await createEnvironment(environmentName); + await selectEnvironmentForNotebook(environmentName); + + const renderedOutput = await runAndAwaitOutput(EXPECTED_OUTPUT, OUTPUT_TIMEOUT); + expect(renderedOutput).to.contain(EXPECTED_OUTPUT); + }); + + /** + * Drives `deepnote.environments.create`: pick interpreter -> name -> skip packages -> + * skip description. Retries when the Python extension has not finished discovering an + * interpreter yet (the command shows an error and returns instead of opening a quick pick). + */ + async function createEnvironment(name: string): Promise { + let lastError: unknown; + + for (let attempt = 1; attempt <= MAX_CREATE_ATTEMPTS; attempt++) { + await new Workbench().executeCommand(CREATE_ENV_COMMAND); + + // Either the interpreter quick pick opens, or (no interpreter discovered yet) the + // command shows a "No Python interpreters found" notification and returns. + const interpreterPick = await tryOpenInputBox(5_000); + if (!interpreterPick) { + await dismissAllNotifications(); + await driver.sleep(INTERPRETER_RETRY_DELAY); + lastError = new Error('interpreter quick pick did not appear (interpreter discovery not ready?)'); + continue; + } + + try { + await driver.wait( + async () => (await interpreterPick.getQuickPicks()).length > 0, + QUICK_PICK_TIMEOUT, + 'no Python interpreters were listed' + ); + } catch (error) { + await interpreterPick.cancel().catch(() => undefined); + await dismissAllNotifications(); + await driver.sleep(INTERPRETER_RETRY_DELAY); + lastError = error; + continue; + } + + await interpreterPick.selectQuickPick(0); + + const nameBox = await InputBox.create(); + await nameBox.setText(name); + await nameBox.confirm(); + + // Packages (optional) — leave empty. + await (await InputBox.create()).confirm(); + + // Description (optional) — leave empty. + await (await InputBox.create()).confirm(); + + // Treat both the success toast and the "already exists" guard as success: a leftover + // environment from a previous/retried run is fine — it will be selected next. + await waitForNotification(/created successfully|already exists/i, ENV_CREATED_TIMEOUT, false); + return; + } + + throw new Error( + `Failed to create a Deepnote environment after ${MAX_CREATE_ATTEMPTS} attempts. ` + + `Ensure the Python extension is installed and an interpreter is discoverable. ` + + `Last error: ${String(lastError)}` + ); + } + + /** + * Drives `deepnote.environments.selectForNotebook`. Selecting the environment rebuilds and + * explicitly selects the notebook's kernel controller (provisioning the venv + toolkit), + * which is what "wait for the kernel to connect" means in this extension. + */ + async function selectEnvironmentForNotebook(name: string): Promise { + // The command requires an active `deepnote` notebook — make sure it's focused. + await new EditorView().openEditor(NOTEBOOK_FILE_NAME); + + // Clear the "select an environment" prompt and any other toasts; they can overlap the + // quick pick and intercept clicks. + await dismissAllNotifications(); + + await new Workbench().executeCommand(SELECT_ENV_COMMAND); + + const environmentPick = await InputBox.create(QUICK_PICK_TIMEOUT); + // Filter to the environment by name and accept with Enter rather than clicking the row: + // the quick-pick row contains a description `

` that can intercept a positional click. + await environmentPick.setText(name); + await driver.wait( + async () => (await environmentPick.getQuickPicks()).length > 0, + QUICK_PICK_TIMEOUT, + 'environment quick pick was empty' + ); + await environmentPick.confirm(); + + // Best-effort wait for the "switched successfully" toast; the authoritative gate is the + // rendered output below, so a missed (auto-dismissed) toast must not fail the test. + await waitForNotification(/switched successfully/i, KERNEL_CONNECT_TIMEOUT, false); + } + + /** + * Clicks the notebook editor toolbar's "Run All" button. The command-palette entry for + * `deepnote.runallcells` ("Jupyter: Run All Cells") is gated behind context keys + * (`deepnote.ispythonornativeactive`, …) that are not reliably set under automation, so driving + * it through `Workbench.executeCommand` can silently miss and trigger the wrong command. + */ + async function clickRunAll(): Promise { + await new EditorView().openEditor(NOTEBOOK_FILE_NAME); + + const runAllButton = await driver.wait( + async () => { + const [button] = await driver.findElements(By.css('a.action-label[aria-label="Run All"]')); + + return button; + }, + WORKBENCH_TIMEOUT, + 'notebook "Run All" button did not appear' + ); + await runAllButton.click(); + } + + /** + * Opens a file that lives in the currently-open workspace folder via Quick Open ("Go to + * File..."), matching by file name. Unlike the simple Open File dialog (where Enter does not + * accept a typed path), Quick Open reliably opens the highlighted match on confirm. + */ + async function openWorkspaceFile(fileName: string): Promise { + await new Workbench().executeCommand('Go to File...'); + + const quickOpen = await InputBox.create(QUICK_PICK_TIMEOUT); + await quickOpen.setText(fileName); + await driver.wait( + async () => (await quickOpen.getQuickPicks()).length > 0, + QUICK_PICK_TIMEOUT, + `"${fileName}" did not appear in Quick Open` + ); + await quickOpen.confirm(); + } + + /** + * Opens an absolute folder path as the workspace root via "File: Open Folder...". Opening a + * folder reloads the VS Code window. In the simple folder dialog, Enter navigates *into* a + * directory rather than accepting it as the workspace — the deterministic accept is the dialog's + * "OK" button — so we type the path, click OK, and wait for the pre-reload workbench element to + * detach (reload started). We retry the whole interaction defensively. The caller then waits for + * the new workbench to mount. + */ + async function openFolderViaDialog(folder: string): Promise { + for (let attempt = 1; attempt <= FOLDER_OPEN_ATTEMPTS; attempt++) { + const previousWorkbench = await driver.findElement(By.css('.monaco-workbench')); + + await new Workbench().executeCommand('File: Open Folder...'); + const dialog = await InputBox.create(QUICK_PICK_TIMEOUT); + await dialog.setText(folder); + + // The simple dialog resolves the typed path asynchronously (listing the enclosing + // directory); wait for that listing and add a short settle before accepting. + await driver + .wait( + async () => (await dialog.getQuickPicks()).length > 0, + QUICK_PICK_TIMEOUT, + 'dialog did not resolve path' + ) + .catch(() => undefined); + await driver.sleep(DIALOG_RESOLVE_DELAY); + + const accepted = await clickDialogOkButton(); + if (!accepted) { + await new InputBox().cancel().catch(() => undefined); + continue; + } + + const reloaded = await driver + .wait(async () => { + try { + await previousWorkbench.getTagName(); + + return false; + } catch { + return true; + } + }, FOLDER_RELOAD_TIMEOUT) + .then(() => true) + .catch(() => false); + if (reloaded) { + return; + } + + // The folder did not open this time; dismiss any lingering dialog and retry. + await new InputBox().cancel().catch(() => undefined); + } + + throw new Error(`Failed to open folder "${folder}" after ${FOLDER_OPEN_ATTEMPTS} attempts`); + } + + /** Clicks the simple file/folder dialog's "OK" button. Returns false if it could not be found. */ + async function clickDialogOkButton(): Promise { + const buttons = await driver + .findElements(By.css('.quick-input-widget .monaco-button.monaco-text-button')) + .catch(() => []); + for (const button of buttons) { + const text = (await button.getText().catch(() => '')).trim(); + if (text === 'OK') { + await button.click(); + + return true; + } + } + + return false; + } + + /** + * Clicks "Run All" and polls the notebook output webview until the expected text renders, + * re-issuing "Run All" periodically. The first run can be dropped when the kernel has only just + * finished connecting, so we keep nudging it until output appears (re-running `print(...)` is + * harmless). + */ + async function runAndAwaitOutput(expected: string, timeout: number): Promise { + const deadline = Date.now() + timeout; + let lastRunAt = 0; + let lastText = ''; + + while (Date.now() < deadline) { + if (Date.now() - lastRunAt > RUN_ALL_REISSUE_INTERVAL) { + await clickRunAll().catch(() => undefined); + lastRunAt = Date.now(); + } + + lastText = await readRenderedOutput(); + if (lastText.includes(expected)) { + return lastText; + } + + await driver.sleep(2_000); + } + + throw new Error( + `Timed out after ${timeout}ms waiting for rendered output to contain "${expected}". ` + + `Last observed output: ${JSON.stringify(lastText)}` + ); + } + + /** + * Reads the notebook cell output once. + * + * Output lives two iframes deep (iframe.webview.ready -> #active-frame). We only attempt to + * switch when an output webview iframe actually exists (`getViewToSwitchTo`), and we read + * output-specific elements inside the frame — so we never match the cell's source code that + * is visible in the editor of the main document. Returns '' when no output is present yet. + */ + async function readRenderedOutput(): Promise { + const webView = new WebView(); + const outputFrame = await webView.getViewToSwitchTo().catch(() => undefined); + if (!outputFrame) { + return ''; + } + + let text = ''; + try { + await webView.switchToFrame(5_000); + const elements = await webView.findWebElements(By.css(OUTPUT_SELECTOR)); + const texts = await Promise.all(elements.map((element) => element.getText().catch(() => ''))); + text = texts.join('\n').trim(); + + // Fallback: if the renderer used unexpected classes, read the frame body — safe here + // because we have confirmed we are inside the output iframe, not the editor. + if (!text) { + const body = await webView.findWebElement(By.css('body')).catch(() => undefined); + text = body ? (await body.getText().catch(() => '')).trim() : ''; + } + } catch { + // Frame went stale or output not painted yet — treat as no output this tick. + } finally { + await webView.switchBack().catch(() => undefined); + } + + return text; + } + + async function tryOpenInputBox(timeout: number): Promise { + try { + return await InputBox.create(timeout); + } catch { + return undefined; + } + } + + async function dismissAllNotifications(): Promise { + const notifications = await new Workbench().getNotifications().catch(() => [] as Notification[]); + for (const notification of notifications) { + await notification.dismiss().catch(() => undefined); + } + } + + async function waitForNotification( + pattern: RegExp, + timeout: number, + required: boolean + ): Promise { + try { + return (await driver.wait( + async () => { + const notifications = await new Workbench().getNotifications().catch(() => [] as Notification[]); + for (const notification of notifications) { + const message = await notification.getMessage().catch(() => ''); + if (pattern.test(message)) { + return notification; + } + } + return undefined; + }, + timeout, + `timed out waiting for a notification matching ${pattern}` + )) as Notification; + } catch (error) { + if (required) { + throw error; + } + return undefined; + } + } +}); +``` + +### 6.8 `test/e2e/enable-proposed-api.js` + +New file: allow-lists the extension's proposed APIs in the downloaded VS Code's +`product.json` (see §0). Run via `npm run setup:e2e:proposed-api` after the test VS Code +has been downloaded; idempotent. + +```js +// Enables the extension's proposed VS Code APIs in the ExTester-managed VS Code instance. +// +// The Deepnote extension declares `enabledApiProposals` in package.json (notebook kernel / +// execution APIs, etc.). VS Code blocks proposed APIs for a normally-installed extension unless +// it is launched in extension-development mode or with `--enable-proposed-api`, neither of which +// ExTester's `setup-and-run` exposes. The supported, black-box-friendly alternative is the +// `extensionEnabledApiProposals` allowlist in the downloaded VS Code's `product.json` — the very +// mechanism stable VS Code uses to allow Microsoft extensions (e.g. ms-python.python) to use +// proposed APIs. This script writes our extension into that allowlist, idempotently. +// +// It must run AFTER VS Code has been downloaded (`extest get-vscode`) and is safe to re-run. +// ExTester caches VS Code under `os.tmpdir()/test-resources` (override with TEST_RESOURCES), and +// `setup-and-run` does not re-extract a cached VS Code, so the patch survives subsequent runs. + +const fs = require('fs'); +const os = require('os'); +const path = require('path'); + +const extensionManifest = require('../../package.json'); + +function getStorageFolder() { + return process.env.TEST_RESOURCES ? process.env.TEST_RESOURCES : path.join(os.tmpdir(), 'test-resources'); +} + +// Locate `*/resources/app/product.json` under the storage folder across platforms +// (VSCode-linux-x64, VSCode-win32-x64, "Visual Studio Code.app/Contents", …). +function findProductJson(storageFolder) { + if (!fs.existsSync(storageFolder)) { + return undefined; + } + + for (const entry of fs.readdirSync(storageFolder)) { + const base = path.join(storageFolder, entry); + + for (const candidate of [ + path.join(base, 'resources', 'app', 'product.json'), + path.join(base, 'Contents', 'Resources', 'app', 'product.json') + ]) { + if (fs.existsSync(candidate)) { + return candidate; + } + } + } + + return undefined; +} + +function main() { + const proposals = extensionManifest.enabledApiProposals; + if (!Array.isArray(proposals) || proposals.length === 0) { + console.log('No enabledApiProposals declared in package.json; nothing to do.'); + return; + } + + const storageFolder = getStorageFolder(); + const productJsonPath = findProductJson(storageFolder); + if (!productJsonPath) { + console.error( + `Could not find a VS Code product.json under ${storageFolder}. ` + + `Download VS Code first (e.g. \`npm run setup:e2e:vscode\`), then re-run this script.` + ); + process.exit(1); + } + + const product = JSON.parse(fs.readFileSync(productJsonPath, 'utf8')); + product.extensionEnabledApiProposals = product.extensionEnabledApiProposals || {}; + + // Match on the canonical id and its lowercase form: VS Code compares extension ids + // case-insensitively (ExtensionIdentifier.toKey lowercases), and our publisher is capitalized. + const extensionId = `${extensionManifest.publisher}.${extensionManifest.name}`; + for (const id of new Set([extensionId, extensionId.toLowerCase()])) { + product.extensionEnabledApiProposals[id] = proposals; + } + + fs.writeFileSync(productJsonPath, `${JSON.stringify(product, null, '\t')}\n`); + + console.log(`Enabled proposed APIs for ${extensionId} in ${productJsonPath}`); + console.log(`Proposals: ${proposals.join(', ')}`); +} + +main(); +``` + +--- + +## 7. How the hard parts work + +### 7.1 Reading rendered output from nested iframes + +ExTester has **no notebook page object**. VS Code renders cell output inside **two nested +iframes**: + +``` +main VS Code document +└─ iframe.webview.ready ← outer (ExTester locator: iframe[class='webview ready']) + └─ iframe#active-frame ← inner (ExTester locator: #active-frame) + └─ .output_container .output ← the rendered output lives here +``` + +ExTester's `WebView.switchToFrame()` descends **exactly those two levels** (verified in its +`WebviewMixin` source), which is why it lines up with the notebook output area. The test's +`waitForRenderedOutput` helper: + +1. Constructs `new WebView()` and calls `getViewToSwitchTo()` — this returns the outer + webview iframe **only if one exists**. The output iframe is created lazily once a cell + produces output, so before execution it returns `undefined` and we simply keep polling. + **This gate is what prevents a false positive**: without it, `switchToFrame()` would + no-op (stay in the main document) and reading the body would match the cell's *source* + `print("hello world")` shown in the editor. +2. Once a frame exists, `switchToFrame(5_000)` descends both levels. +3. Reads **output-scoped** elements (`.output_container, .output, .rendered-output`) — not + the whole body — and joins their text. (A body-text fallback is used only when those + classes are absent, and it's safe there because we've confirmed we are inside the output + iframe.) +4. **Always `switchBack()` in `finally`** — touching the main document while switched into + a frame throws `StaleElementReferenceError`. +5. Loops until the text contains `hello world` or the timeout elapses. + +**Fallback if `WebView` ever mis-targets the frame** (e.g. VS Code adds a class so the +exact `iframe[class='webview ready']` locator misses): drive Selenium directly — +`driver.switchTo().frame(driver.findElement(By.css('iframe.webview')))` then +`driver.switchTo().frame(driver.findElement(By.id('active-frame')))`, read, then +`driver.switchTo().defaultContent()`. + +**Note on shadow DOM:** rich/widget renderers may render into a shadow root (unreachable by +plain CSS). Plain stdout text (our case) renders in light DOM and is reachable. + +### 7.2 Driving QuickPicks & InputBoxes + +`Workbench.executeCommand(label)` opens the palette and matches the **friendly command +title** (category + title), so we pass e.g. `'Deepnote: Create Environment'`, not the +command id. Since VS Code 1.44, `InputBox` represents both text prompts and QuickPicks; we +re-create it between steps because each step replaces the DOM. + +Flakiness guards built into the test: + +- **Interpreter-discovery latency.** The Python extension populates + `api.environments.known` asynchronously; if empty, the create command shows *"No Python + interpreters found"* and returns (no quick pick). The test detects the missing quick pick + (`tryOpenInputBox` times out), dismisses notifications, waits, and **retries up to 6 + times**. +- **Unique environment name per run** (`E2E Hello Env `) so a leftover env from + a Mocha retry can't trip the "name already exists" guard. +- **Notification waits are best-effort** for the transient success toasts; the authoritative + gate is the rendered output, so an auto-dismissed toast never fails the test. +- **`driver.wait(...)`** is used for every asynchronous UI state instead of bare sleeps + where possible. + +--- + +## 8. Running it + +`extest setup-and-run` loads the extension from the **built** `dist/` output and launches a +real VS Code window. First-time sequence: + +```bash +npm run compile # build the extension under test (produces dist/extension.node.js) +npm run setup:e2e # download test VS Code + ChromeDriver, install ms-python.python, + # and allow-list proposed APIs (§0) — one-time, idempotent +npm run test:e2e # pretest compiles test/e2e → out/e2e, then extest packages & runs +``` + +- **Linux is headless** → install the Electron/Chromium system libraries and Xvfb (§0), then + wrap the run: + ```bash + xvfb-run --auto-servernum --server-args='-screen 0 1920x1080x24' npm run test:e2e + ``` + macOS/Windows run directly. (ExTester always launches VS Code with `--no-sandbox`, so the + Ubuntu 24.04 AppArmor user-namespace restriction does not block the test — no sysctl needed; + inside a container where that sysctl is read-only, `--no-sandbox` is what makes it work.) +- A **venv-capable Python interpreter** must be discoverable (§0), and creating the environment + installs the Deepnote toolkit (**network required**); the first kernel start can take minutes. +- ExTester caches VS Code/ChromeDriver under `test-resources/` (by default + `$TMPDIR/test-resources`, e.g. `/tmp/test-resources`) after the first download. +- Failure screenshots are written under `test-resources/**/_screenshots/` (and any + `_screenshots` under `out/e2e/`). + +**Compatibility note:** ExTester `8.23.0` supports a floating window of recent VS Code +minors; `-c max` picks the newest. Our extension's `engines.vscode` is `^1.95.0`, so any +1.x ≥ 1.95 (including `max`) is compatible. Node should be an active LTS (the repo's +`.nvmrc`; local dev uses Node 22). + +--- + +## 9. CI integration + +Add a dedicated job (separate from lint/typecheck/unit so its weight and flakiness are +isolated). Linux must run under a virtual framebuffer. + +```yaml +e2e: + name: E2E (ExTester) + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: { cache: npm, node-version-file: .nvmrc } + - uses: actions/setup-python@v5 # interpreter for the Deepnote environment + with: { python-version: '3.12' } + - run: npm ci --prefer-offline --no-audit + - run: npm run compile # build the extension under test + # Electron/Chromium runtime libraries (the test VS Code won't launch without them) + Xvfb + - run: | + sudo apt-get update + sudo apt-get install -y xvfb \ + libatk1.0-0t64 libatk-bridge2.0-0t64 libcups2t64 libgtk-3-0t64 libgdk-pixbuf-2.0-0 \ + libgbm1 libasound2t64 libnss3 libnspr4 libxss1 libxshmfence1 libdrm2 libxkbcommon0 \ + libxcomposite1 libxdamage1 libxrandr2 libxfixes3 libxext6 libxrender1 libpango-1.0-0 \ + libcairo2 libatspi2.0-0 libx11-xcb1 libxcb-dri3-0 libxtst6 libsecret-1-0 \ + libgssapi-krb5-2 libdbus-1-3 libexpat1 python3.12-venv python3-pip + # Download the test VS Code, install ms-python.python, and allow-list proposed APIs (§0). + # ExTester always launches with --no-sandbox, so no AppArmor sysctl is required. + - run: npm run setup:e2e + - name: Run E2E + uses: nick-fields/retry@v4 # absorb transient UI flakiness + with: + timeout_minutes: 40 + max_attempts: 2 + command: xvfb-run --auto-servernum --server-args='-screen 0 1920x1080x24' npm run test:e2e + - uses: actions/upload-artifact@v7 + if: failure() + with: + name: e2e-screenshots + path: | + test-resources/**/_screenshots/**/*.png + out/e2e/**/_screenshots/**/*.png +``` + +Notes: +- ExTester caches VS Code/ChromeDriver under `test-resources/` itself; optionally cache that + directory to speed reruns. +- On macOS/Windows runners drop the `xvfb-run` wrapper. +- **Network**: creating the environment installs the Deepnote toolkit (pip). The runner + needs outbound network, or a pre-seeded/offline toolkit — the single biggest portability + risk; flag it when enabling the job. + +--- + +## 10. Gotchas & flakiness mitigation + +- **Bump timeouts.** Mocha's 2 s default is unusable; the suite uses `timeout: 1500000` (25 min) + and the test sets a 22-minute per-test timeout (overrides the suite default). +- **Prefer `executeCommand` over clicking** *for palette commands that are always enabled* — but + some are not. `deepnote.runallcells` is gated behind context keys that don't hold under + automation, so the test clicks the toolbar's "Run All" button instead (§0). +- **The simple file dialog accepts via its "OK" button, not Enter.** Enter navigates into a + directory. Type the path, then click `.quick-input-widget .monaco-button.monaco-text-button` + whose text is "OK" (§0, `clickDialogOkButton`). +- **Re-issue "Run All"** while polling for output — the first run can be dropped right after the + kernel connects (§0, `runAndAwaitOutput`). +- **Always `switchBack()` in `finally`** around any webview interaction. +- **Use the safe constructors** (`await InputBox.create()`, `await EditorView().openEditor(...)`) + rather than `new InputBox()` when the element may not be ready. +- **Clean up** in `after`: `switchBack()` defensively and `closeAllEditors()`. +- **macOS caveat** (if you extend the suite): native title-bar menus, native context menus, + and native file dialogs are unsupported by ExTester — use command-palette equivalents and + VS Code "simple" dialogs (we already force `files.simpleDialog.enable`). +- **`getCurrentChannel`/`getLaunchConfiguration`** are broken on Windows/Linux for VS Code + ≥ 1.87 — avoid them. +- **chai v5** is ESM-only — stay on chai v4 (the repo's version). + +--- + +## 11. Where ExTester fits vs the other test layers + +This repo already has three layers; ExTester is a fourth that fills the "real rendered UX" +gap. It **replaces nothing**. + +| Layer | Runner | Covers | +| --- | --- | --- | +| Unit (`*.unit.test.ts`) | Mocha + Chai (`build/.mocha.unittests.js.json`) | Pure logic, no VS Code host. | +| Integration (`*.vscode.test.ts`) | `@vscode/test-electron` (`src/test/standardTest.node.ts`) | Runs **inside** the extension host with the `vscode` API. | +| Smoke (`src/smoke`) | custom harness | Broad checks. | +| **E2E (this) (`*.e2e.test.ts`)** | **ExTester** (`test/e2e`) | **Black-box, real VS Code UI** — open → env → kernel → run → rendered output. | + +**Rule of thumb:** assert *rendered pixels* with ExTester; assert *output data/semantics* +with the integration layer. For output-heavy correctness, the in-host API route (what +upstream microsoft/vscode-jupyter does — drive `notebook.cell.execute`, read +`cell.outputs[].items[].data` via `TextDecoder`, no iframes) is more reliable; add such +tests as the volume layer over time. Reserve ExTester for a *small* number of high-value +end-to-end smoke tests like the one shipped here. + +--- + +## 12. Risks & mitigations + +| Risk | Mitigation | +| --- | --- | +| No notebook page object in ExTester | Use `WebView` (its 2-level frame switch matches the notebook output iframes); assert on output-scoped selectors; raw-Selenium fallback documented (§7.1). | +| Reading editor source as if it were output | Gate frame switching on `getViewToSwitchTo()` and read output-scoped selectors only (§7.1). | +| Python interpreter not discovered in time | Install `ms-python.python`; retry the create command up to 6×; `setup-python` in CI. | +| Env creation needs network (pip toolkit) | Documented prerequisite; consider an offline/seeded toolkit for CI. | +| First kernel start is slow | Long polls (5 min on output / kernel connect); 13-min per-test timeout; `nick-fields/retry`. | +| Linux GUI / sandbox | `xvfb-run`; AppArmor sysctl on Ubuntu 24.04. | +| UI flakiness | `driver.wait` everywhere; `switchBack` in `finally`; screenshots on failure; one Mocha retry. | +| chai v5 ESM breakage | Pin chai v4 (already the repo's version). | +| Workspace-trust modal blocks automation | `security.workspace.trust.enabled: false` in test settings. | + +--- + +## Appendix A — ExTester API surface used + +All imported from `vscode-extension-tester` (re-exported from `@redhat-developer/page-objects`, +`@redhat-developer/locators`, and `selenium-webdriver`). Signatures verified against the +installed `8.23.0`. + +| Symbol | Member | Notes | +| --- | --- | --- | +| `VSBrowser` | `instance` | singleton | +| | `instance.driver` | the selenium `WebDriver` (`.wait`, `.sleep`, …) | +| | `openResources(...paths, cb?)` | open files/folders (absolute paths) | +| | `waitForWorkbench(timeout?)` | wait until workbench is ready | +| | `takeScreenshot(name)` | manual screenshot → `/_screenshots` | +| `Workbench` | `executeCommand(label)` | matches the friendly command **title** | +| | `getNotifications()` | current toast notifications | +| `EditorView` | `openEditor(title)` | focus/return an editor tab | +| | `getOpenEditorTitles()` | open tab titles | +| | `closeAllEditors()` | cleanup | +| `InputBox` | `static create(timeout?)` | safe constructor; represents prompts **and** QuickPicks | +| | `setText` / `confirm` / `cancel` | text prompts | +| | `getQuickPicks()` / `selectQuickPick(idxOrText)` / `findQuickPick` | quick picks (substring match) | +| | `hasProgress()` | true while the input shows a progress bar | +| `WebView` | `getViewToSwitchTo()` | returns the outer webview iframe element or `undefined` | +| | `switchToFrame(timeout?)` / `switchBack()` | descend into / out of the (2-level) webview iframes | +| | `findWebElement(locator)` / `findWebElements(locator)` | query inside the frame | +| `Notification` | `getMessage()` / `getType()` / `dismiss()` / `takeAction(title)` | toast inspection | +| `By`, `until`, `Key`, `WebDriver` | — | re-exported selenium primitives | + +--- + +## Appendix B — Deepnote command-id reference + +The commands this test drives, with their command ids and palette labels (from +`package.json` + `package.nls.json`): + +| Palette label (used in the test) | Command id | Category | +| --- | --- | --- | +| `Deepnote: Create Environment` | `deepnote.environments.create` | Deepnote | +| `Deepnote: Select Environment for Notebook` | `deepnote.environments.selectForNotebook` | Deepnote | +| `Jupyter: Run All Cells` | `deepnote.runallcells` | Jupyter | + +Notebook type registered for `.deepnote`: **`deepnote`** +(`contributes.notebooks[].type`, selector `*.deepnote`). + +--- + +## References + +ExTester: +- Repo / wiki: https://github.com/redhat-developer/vscode-extension-tester · + https://github.com/redhat-developer/vscode-extension-tester/wiki +- Test setup (CLI): https://github.com/redhat-developer/vscode-extension-tester/wiki/Test-Setup +- WebView page object: https://github.com/redhat-developer/vscode-extension-tester/wiki/WebView +- Workbench / Input: https://github.com/redhat-developer/vscode-extension-tester/wiki/Workbench · + https://github.com/redhat-developer/vscode-extension-tester/wiki/Input +- Example project: https://github.com/redhat-developer/vscode-extension-tester-example +- Known issues (AppArmor, etc.): https://github.com/redhat-developer/vscode-extension-tester/blob/main/KNOWN_ISSUES.md + +Real-world usage studied: redhat-developer/{vscode-quarkus, vscode-server-connector, +vscode-rsp-ui} and the framework's own `tests/test-project`. + +VS Code notebook internals (output DOM / iframes): VS Code source `webviewPreloads.ts`, +`pre/index.html`; Notebook API guide +https://code.visualstudio.com/api/extension-guides/notebook ; built-in commands +https://code.visualstudio.com/api/references/commands + +Upstream test approach (API-driven alternative): microsoft/vscode-jupyter +`src/test/datascience/notebook/helper.ts`. + +Codebase anchors: `src/notebooks/deepnote/deepnoteKernelAutoSelector.node.ts`, +`src/kernels/deepnote/environments/deepnoteEnvironmentsView.node.ts`, +`src/notebooks/deepnote/deepnoteSerializer.ts`. diff --git a/test/e2e/.mocharc.js b/test/e2e/.mocharc.js new file mode 100644 index 0000000000..c2a25f09ae --- /dev/null +++ b/test/e2e/.mocharc.js @@ -0,0 +1,9 @@ +// Mocha configuration for the ExTester (vscode-extension-tester) E2E suite. +// UI tests are slow: the 2s Mocha default is unusable. Individual waits inside the +// tests are the real guard rails; this is a generous suite-level safety net. +module.exports = { + timeout: 1500000, // 25 min — env creation + first kernel start (venv + toolkit) can be slow + retries: 1, // absorb transient UI flakiness with a single retry + reporter: 'spec', + color: true +}; diff --git a/test/e2e/enable-proposed-api.js b/test/e2e/enable-proposed-api.js new file mode 100644 index 0000000000..60e996f9ca --- /dev/null +++ b/test/e2e/enable-proposed-api.js @@ -0,0 +1,81 @@ +// Enables the extension's proposed VS Code APIs in the ExTester-managed VS Code instance. +// +// The Deepnote extension declares `enabledApiProposals` in package.json (notebook kernel / +// execution APIs, etc.). VS Code blocks proposed APIs for a normally-installed extension unless +// it is launched in extension-development mode or with `--enable-proposed-api`, neither of which +// ExTester's `setup-and-run` exposes. The supported, black-box-friendly alternative is the +// `extensionEnabledApiProposals` allowlist in the downloaded VS Code's `product.json` — the very +// mechanism stable VS Code uses to allow Microsoft extensions (e.g. ms-python.python) to use +// proposed APIs. This script writes our extension into that allowlist, idempotently. +// +// It must run AFTER VS Code has been downloaded (`extest get-vscode`) and is safe to re-run. +// ExTester caches VS Code under `os.tmpdir()/test-resources` (override with TEST_RESOURCES), and +// `setup-and-run` does not re-extract a cached VS Code, so the patch survives subsequent runs. + +const fs = require('fs'); +const os = require('os'); +const path = require('path'); + +const extensionManifest = require('../../package.json'); + +function getStorageFolder() { + return process.env.TEST_RESOURCES ? process.env.TEST_RESOURCES : path.join(os.tmpdir(), 'test-resources'); +} + +// Locate `*/resources/app/product.json` under the storage folder across platforms +// (VSCode-linux-x64, VSCode-win32-x64, "Visual Studio Code.app/Contents", …). +function findProductJson(storageFolder) { + if (!fs.existsSync(storageFolder)) { + return undefined; + } + + for (const entry of fs.readdirSync(storageFolder)) { + const base = path.join(storageFolder, entry); + + for (const candidate of [ + path.join(base, 'resources', 'app', 'product.json'), + path.join(base, 'Contents', 'Resources', 'app', 'product.json') + ]) { + if (fs.existsSync(candidate)) { + return candidate; + } + } + } + + return undefined; +} + +function main() { + const proposals = extensionManifest.enabledApiProposals; + if (!Array.isArray(proposals) || proposals.length === 0) { + console.log('No enabledApiProposals declared in package.json; nothing to do.'); + return; + } + + const storageFolder = getStorageFolder(); + const productJsonPath = findProductJson(storageFolder); + if (!productJsonPath) { + console.error( + `Could not find a VS Code product.json under ${storageFolder}. ` + + `Download VS Code first (e.g. \`npm run setup:e2e:vscode\`), then re-run this script.` + ); + process.exit(1); + } + + const product = JSON.parse(fs.readFileSync(productJsonPath, 'utf8')); + product.extensionEnabledApiProposals = product.extensionEnabledApiProposals || {}; + + // Match on the canonical id and its lowercase form: VS Code compares extension ids + // case-insensitively (ExtensionIdentifier.toKey lowercases), and our publisher is capitalized. + const extensionId = `${extensionManifest.publisher}.${extensionManifest.name}`; + for (const id of new Set([extensionId, extensionId.toLowerCase()])) { + product.extensionEnabledApiProposals[id] = proposals; + } + + fs.writeFileSync(productJsonPath, `${JSON.stringify(product, null, '\t')}\n`); + + console.log(`Enabled proposed APIs for ${extensionId} in ${productJsonPath}`); + console.log(`Proposals: ${proposals.join(', ')}`); +} + +main(); diff --git a/test/e2e/fixtures/hello-world.deepnote b/test/e2e/fixtures/hello-world.deepnote new file mode 100644 index 0000000000..72f4c4b6d1 --- /dev/null +++ b/test/e2e/fixtures/hello-world.deepnote @@ -0,0 +1,21 @@ +version: '1.0.0' +metadata: + createdAt: '2025-01-01T00:00:00.000Z' + modifiedAt: '2025-01-01T00:00:00.000Z' +project: + id: e2e-hello-world-project + name: E2E Hello World + notebooks: + - id: e2e-hello-world-notebook + name: Hello World + blocks: + - id: e2e-hello-block + blockGroup: e2e-group + type: code + content: |- + print("hello world") + sortingKey: a0 + metadata: {} + executionMode: block + isModule: false + settings: {} diff --git a/test/e2e/settings.json b/test/e2e/settings.json new file mode 100644 index 0000000000..00b1470973 --- /dev/null +++ b/test/e2e/settings.json @@ -0,0 +1,13 @@ +{ + "files.simpleDialog.enable": true, + "window.dialogStyle": "custom", + "workbench.editor.enablePreview": false, + "workbench.startupEditor": "none", + "extensions.ignoreRecommendations": true, + "workbench.remoteIndicator.showExtensionRecommendations": false, + "git.autoRepositoryDetection": false, + "telemetry.telemetryLevel": "off", + "update.mode": "none", + "jupyter.kernels.trusted": true, + "security.workspace.trust.enabled": false +} diff --git a/test/e2e/suite/helloWorld.e2e.test.ts b/test/e2e/suite/helloWorld.e2e.test.ts new file mode 100644 index 0000000000..3d3fdb4777 --- /dev/null +++ b/test/e2e/suite/helloWorld.e2e.test.ts @@ -0,0 +1,439 @@ +/** + * End-to-end UI test driven by ExTester (vscode-extension-tester). + * + * It exercises the full Deepnote happy path through the *real* VS Code UI: + * 1. open a one-notebook `.deepnote` file containing `print("hello world")` + * 2. create a Deepnote environment (command `deepnote.environments.create`) + * 3. select that environment for the notebook (command `deepnote.environments.selectForNotebook`) + * — this builds and selects the notebook's kernel controller ("kernel connected") + * 4. run the cell (the notebook toolbar's "Run All" button) + * 5. assert the rendered stdout output contains "hello world" + * + * Prerequisites (see specs/e2e-extester-testing-plan.md): + * - The Python extension (`ms-python.python`) must be installed in the test instance + * (`npm run setup:e2e:deps`) and at least one Python interpreter must be discoverable. + * - Creating the environment provisions a venv and the Deepnote toolkit, which needs + * network access; the first kernel start can take a few minutes. + * + * Notebook output in VS Code renders inside two nested iframes + * (iframe.webview.ready -> #active-frame). ExTester's WebView page object descends + * exactly those two levels, which is how we read the rendered output below. + */ + +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; + +import { expect } from 'chai'; +import { + By, + EditorView, + InputBox, + Notification, + VSBrowser, + WebView, + Workbench, + type WebDriver +} from 'vscode-extension-tester'; + +// Command palette labels (category + title) the way `Workbench.executeCommand` matches them. +const CREATE_ENV_COMMAND = 'Deepnote: Create Environment'; +const SELECT_ENV_COMMAND = 'Deepnote: Select Environment for Notebook'; + +const NOTEBOOK_FILE_NAME = 'hello-world.deepnote'; +const EXPECTED_OUTPUT = 'hello world'; + +// Timeouts (ms). UI ops are slow and the first kernel start is the slowest step. +const WORKBENCH_TIMEOUT = 60_000; +const QUICK_PICK_TIMEOUT = 30_000; +const ENV_CREATED_TIMEOUT = 120_000; +const KERNEL_CONNECT_TIMEOUT = 300_000; +const OUTPUT_TIMEOUT = 300_000; +// How often to re-issue "Run All" while waiting for output — the first run can be dropped right +// after the kernel connects. +const RUN_ALL_REISSUE_INTERVAL = 25_000; +const INTERPRETER_RETRY_DELAY = 5_000; +const MAX_CREATE_ATTEMPTS = 6; +// The in-window simple file/folder dialog needs a beat to resolve a typed path before it accepts. +const DIALOG_RESOLVE_DELAY = 1_500; +const FOLDER_OPEN_ATTEMPTS = 5; +const FOLDER_RELOAD_TIMEOUT = 12_000; + +// Selectors that only exist inside the notebook output iframe (`#active-frame`), +// so reading them cannot accidentally match the cell's source in the editor. +const OUTPUT_SELECTOR = '.output_container, .output, .rendered-output'; + +describe('Deepnote E2E — run "hello world"', function () { + // Per-test timeout for the whole suite (overrides the mocharc default for these tests). + this.timeout(22 * 60 * 1000); + + let driver: WebDriver; + let notebookFile: string; + // A stable name: createEnvironment is idempotent (it treats "already exists" as success), so a + // leftover environment from a previous or retried run is reused rather than colliding — which + // also lets a persistent test instance reuse the already-provisioned venv. + const environmentName = 'E2E Hello Env'; + + before(async function () { + driver = VSBrowser.instance.driver; + + // Open a throwaway copy so execution-dirtied notebook state never touches the source tree. + const source = path.resolve(process.cwd(), 'test', 'e2e', 'fixtures', NOTEBOOK_FILE_NAME); + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'deepnote-e2e-')); + notebookFile = path.join(tempDir, NOTEBOOK_FILE_NAME); + fs.copyFileSync(source, notebookFile); + + await VSBrowser.instance.waitForWorkbench(WORKBENCH_TIMEOUT); + + // Open the temp directory as a workspace folder FIRST. The Deepnote serializer reads a + // "snapshot" during deserialization and, with no workspace folder open, blocks on a + // `showWarningMessage('Cannot read snapshot: No workspace folders found.')` that never + // resolves headlessly — leaving the notebook blank. A workspace folder also provides the + // requirements.txt path the kernel auto-selector needs. (Opening a folder reloads the + // window, so we re-wait for the workbench afterwards.) + await openFolderViaDialog(tempDir); + await VSBrowser.instance.waitForWorkbench(WORKBENCH_TIMEOUT); + + // Open the notebook by driving the running window directly. ExTester's `openResources` + // shells out to `code -r ` (reuse-window over IPC), which silently no-ops in a + // sandboxed/headless environment. Now that the containing folder is the workspace, the + // notebook is reachable by name through Quick Open ("Go to File..."). + await openWorkspaceFile(NOTEBOOK_FILE_NAME); + + // The native notebook editor opens because the extension registers a serializer for + // the `deepnote` notebook type; a single-notebook file resolves to its default notebook. + await driver.wait( + async () => (await new EditorView().getOpenEditorTitles()).some((t) => t.includes(NOTEBOOK_FILE_NAME)), + WORKBENCH_TIMEOUT, + 'Deepnote notebook editor did not open' + ); + }); + + after(async function () { + // Defensive cleanup: never leave the driver stuck inside a webview frame, and close tabs. + await new WebView().switchBack().catch(() => undefined); + await new EditorView().closeAllEditors().catch(() => undefined); + }); + + it('creates an environment, connects the kernel, runs the cell and renders output', async function () { + await createEnvironment(environmentName); + await selectEnvironmentForNotebook(environmentName); + + const renderedOutput = await runAndAwaitOutput(EXPECTED_OUTPUT, OUTPUT_TIMEOUT); + expect(renderedOutput).to.contain(EXPECTED_OUTPUT); + }); + + /** + * Drives `deepnote.environments.create`: pick interpreter -> name -> skip packages -> + * skip description. Retries when the Python extension has not finished discovering an + * interpreter yet (the command shows an error and returns instead of opening a quick pick). + */ + async function createEnvironment(name: string): Promise { + let lastError: unknown; + + for (let attempt = 1; attempt <= MAX_CREATE_ATTEMPTS; attempt++) { + await new Workbench().executeCommand(CREATE_ENV_COMMAND); + + // Either the interpreter quick pick opens, or (no interpreter discovered yet) the + // command shows a "No Python interpreters found" notification and returns. + const interpreterPick = await tryOpenInputBox(5_000); + if (!interpreterPick) { + await dismissAllNotifications(); + await driver.sleep(INTERPRETER_RETRY_DELAY); + lastError = new Error('interpreter quick pick did not appear (interpreter discovery not ready?)'); + continue; + } + + try { + await driver.wait( + async () => (await interpreterPick.getQuickPicks()).length > 0, + QUICK_PICK_TIMEOUT, + 'no Python interpreters were listed' + ); + } catch (error) { + await interpreterPick.cancel().catch(() => undefined); + await dismissAllNotifications(); + await driver.sleep(INTERPRETER_RETRY_DELAY); + lastError = error; + continue; + } + + await interpreterPick.selectQuickPick(0); + + const nameBox = await InputBox.create(); + await nameBox.setText(name); + await nameBox.confirm(); + + // Packages (optional) — leave empty. + await (await InputBox.create()).confirm(); + + // Description (optional) — leave empty. + await (await InputBox.create()).confirm(); + + // Treat both the success toast and the "already exists" guard as success: a leftover + // environment from a previous/retried run is fine — it will be selected next. + await waitForNotification(/created successfully|already exists/i, ENV_CREATED_TIMEOUT, false); + return; + } + + throw new Error( + `Failed to create a Deepnote environment after ${MAX_CREATE_ATTEMPTS} attempts. ` + + `Ensure the Python extension is installed and an interpreter is discoverable. ` + + `Last error: ${String(lastError)}` + ); + } + + /** + * Drives `deepnote.environments.selectForNotebook`. Selecting the environment rebuilds and + * explicitly selects the notebook's kernel controller (provisioning the venv + toolkit), + * which is what "wait for the kernel to connect" means in this extension. + */ + async function selectEnvironmentForNotebook(name: string): Promise { + // The command requires an active `deepnote` notebook — make sure it's focused. + await new EditorView().openEditor(NOTEBOOK_FILE_NAME); + + // Clear the "select an environment" prompt and any other toasts; they can overlap the + // quick pick and intercept clicks. + await dismissAllNotifications(); + + await new Workbench().executeCommand(SELECT_ENV_COMMAND); + + const environmentPick = await InputBox.create(QUICK_PICK_TIMEOUT); + // Filter to the environment by name and accept with Enter rather than clicking the row: + // the quick-pick row contains a description `

` that can intercept a positional click. + await environmentPick.setText(name); + await driver.wait( + async () => (await environmentPick.getQuickPicks()).length > 0, + QUICK_PICK_TIMEOUT, + 'environment quick pick was empty' + ); + await environmentPick.confirm(); + + // Best-effort wait for the "switched successfully" toast; the authoritative gate is the + // rendered output below, so a missed (auto-dismissed) toast must not fail the test. + await waitForNotification(/switched successfully/i, KERNEL_CONNECT_TIMEOUT, false); + } + + /** + * Clicks the notebook editor toolbar's "Run All" button. The command-palette entry for + * `deepnote.runallcells` ("Jupyter: Run All Cells") is gated behind context keys + * (`deepnote.ispythonornativeactive`, …) that are not reliably set under automation, so driving + * it through `Workbench.executeCommand` can silently miss and trigger the wrong command. + */ + async function clickRunAll(): Promise { + await new EditorView().openEditor(NOTEBOOK_FILE_NAME); + + const runAllButton = await driver.wait( + async () => { + const [button] = await driver.findElements(By.css('a.action-label[aria-label="Run All"]')); + + return button; + }, + WORKBENCH_TIMEOUT, + 'notebook "Run All" button did not appear' + ); + await runAllButton.click(); + } + + /** + * Opens a file that lives in the currently-open workspace folder via Quick Open ("Go to + * File..."), matching by file name. Unlike the simple Open File dialog (where Enter does not + * accept a typed path), Quick Open reliably opens the highlighted match on confirm. + */ + async function openWorkspaceFile(fileName: string): Promise { + await new Workbench().executeCommand('Go to File...'); + + const quickOpen = await InputBox.create(QUICK_PICK_TIMEOUT); + await quickOpen.setText(fileName); + await driver.wait( + async () => (await quickOpen.getQuickPicks()).length > 0, + QUICK_PICK_TIMEOUT, + `"${fileName}" did not appear in Quick Open` + ); + await quickOpen.confirm(); + } + + /** + * Opens an absolute folder path as the workspace root via "File: Open Folder...". Opening a + * folder reloads the VS Code window. In the simple folder dialog, Enter navigates *into* a + * directory rather than accepting it as the workspace — the deterministic accept is the dialog's + * "OK" button — so we type the path, click OK, and wait for the pre-reload workbench element to + * detach (reload started). We retry the whole interaction defensively. The caller then waits for + * the new workbench to mount. + */ + async function openFolderViaDialog(folder: string): Promise { + for (let attempt = 1; attempt <= FOLDER_OPEN_ATTEMPTS; attempt++) { + const previousWorkbench = await driver.findElement(By.css('.monaco-workbench')); + + await new Workbench().executeCommand('File: Open Folder...'); + const dialog = await InputBox.create(QUICK_PICK_TIMEOUT); + await dialog.setText(folder); + + // The simple dialog resolves the typed path asynchronously (listing the enclosing + // directory); wait for that listing and add a short settle before accepting. + await driver + .wait( + async () => (await dialog.getQuickPicks()).length > 0, + QUICK_PICK_TIMEOUT, + 'dialog did not resolve path' + ) + .catch(() => undefined); + await driver.sleep(DIALOG_RESOLVE_DELAY); + + const accepted = await clickDialogOkButton(); + if (!accepted) { + await new InputBox().cancel().catch(() => undefined); + continue; + } + + const reloaded = await driver + .wait(async () => { + try { + await previousWorkbench.getTagName(); + + return false; + } catch { + return true; + } + }, FOLDER_RELOAD_TIMEOUT) + .then(() => true) + .catch(() => false); + if (reloaded) { + return; + } + + // The folder did not open this time; dismiss any lingering dialog and retry. + await new InputBox().cancel().catch(() => undefined); + } + + throw new Error(`Failed to open folder "${folder}" after ${FOLDER_OPEN_ATTEMPTS} attempts`); + } + + /** Clicks the simple file/folder dialog's "OK" button. Returns false if it could not be found. */ + async function clickDialogOkButton(): Promise { + const buttons = await driver + .findElements(By.css('.quick-input-widget .monaco-button.monaco-text-button')) + .catch(() => []); + for (const button of buttons) { + const text = (await button.getText().catch(() => '')).trim(); + if (text === 'OK') { + await button.click(); + + return true; + } + } + + return false; + } + + /** + * Clicks "Run All" and polls the notebook output webview until the expected text renders, + * re-issuing "Run All" periodically. The first run can be dropped when the kernel has only just + * finished connecting, so we keep nudging it until output appears (re-running `print(...)` is + * harmless). + */ + async function runAndAwaitOutput(expected: string, timeout: number): Promise { + const deadline = Date.now() + timeout; + let lastRunAt = 0; + let lastText = ''; + + while (Date.now() < deadline) { + if (Date.now() - lastRunAt > RUN_ALL_REISSUE_INTERVAL) { + await clickRunAll().catch(() => undefined); + lastRunAt = Date.now(); + } + + lastText = await readRenderedOutput(); + if (lastText.includes(expected)) { + return lastText; + } + + await driver.sleep(2_000); + } + + throw new Error( + `Timed out after ${timeout}ms waiting for rendered output to contain "${expected}". ` + + `Last observed output: ${JSON.stringify(lastText)}` + ); + } + + /** + * Reads the notebook cell output once. + * + * Output lives two iframes deep (iframe.webview.ready -> #active-frame). We only attempt to + * switch when an output webview iframe actually exists (`getViewToSwitchTo`), and we read + * output-specific elements inside the frame — so we never match the cell's source code that + * is visible in the editor of the main document. Returns '' when no output is present yet. + */ + async function readRenderedOutput(): Promise { + const webView = new WebView(); + const outputFrame = await webView.getViewToSwitchTo().catch(() => undefined); + if (!outputFrame) { + return ''; + } + + let text = ''; + try { + await webView.switchToFrame(5_000); + const elements = await webView.findWebElements(By.css(OUTPUT_SELECTOR)); + const texts = await Promise.all(elements.map((element) => element.getText().catch(() => ''))); + text = texts.join('\n').trim(); + + // Fallback: if the renderer used unexpected classes, read the frame body — safe here + // because we have confirmed we are inside the output iframe, not the editor. + if (!text) { + const body = await webView.findWebElement(By.css('body')).catch(() => undefined); + text = body ? (await body.getText().catch(() => '')).trim() : ''; + } + } catch { + // Frame went stale or output not painted yet — treat as no output this tick. + } finally { + await webView.switchBack().catch(() => undefined); + } + + return text; + } + + async function tryOpenInputBox(timeout: number): Promise { + try { + return await InputBox.create(timeout); + } catch { + return undefined; + } + } + + async function dismissAllNotifications(): Promise { + const notifications = await new Workbench().getNotifications().catch(() => [] as Notification[]); + for (const notification of notifications) { + await notification.dismiss().catch(() => undefined); + } + } + + async function waitForNotification( + pattern: RegExp, + timeout: number, + required: boolean + ): Promise { + try { + return (await driver.wait( + async () => { + const notifications = await new Workbench().getNotifications().catch(() => [] as Notification[]); + for (const notification of notifications) { + const message = await notification.getMessage().catch(() => ''); + if (pattern.test(message)) { + return notification; + } + } + return undefined; + }, + timeout, + `timed out waiting for a notification matching ${pattern}` + )) as Notification; + } catch (error) { + if (required) { + throw error; + } + return undefined; + } + } +}); diff --git a/test/e2e/tsconfig.json b/test/e2e/tsconfig.json new file mode 100644 index 0000000000..39e896d556 --- /dev/null +++ b/test/e2e/tsconfig.json @@ -0,0 +1,21 @@ +{ + // Standalone config for the ExTester E2E suite. It is intentionally independent of the + // extension's root tsconfig (which only compiles ./src) so these tests never enter the + // esbuild bundle or the unit/integration test globs. CommonJS + chai v4 keeps + // `import { expect } from 'chai'` working without ESM friction. + "compilerOptions": { + "module": "commonjs", + "target": "ES2022", + "lib": ["ES2022", "DOM"], + "moduleResolution": "node", + "outDir": "../../out/e2e", + "rootDir": ".", + "sourceMap": true, + "strict": true, + "skipLibCheck": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "types": ["node", "mocha", "chai"] + }, + "include": ["**/*.ts"] +} From 8bc972c4e2a9d8ccbcff7679c3f152b71a3446db Mon Sep 17 00:00:00 2001 From: tomas Date: Wed, 24 Jun 2026 08:40:30 +0000 Subject: [PATCH 2/7] test(e2e): drop the proposed-API product.json patch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verified the extension does not need a proposed-API grant: although it declares Jupyter's `enabledApiProposals`, the core flow — activation, the notebook serializer, kernel execution, and output rendering — runs on stable VS Code APIs (output goes through `controller.createNotebookCellExecution` with a `replaceCells` fallback; the one genuinely-proposed call is guarded). On a plain stable VS Code the proposals are simply ungranted (a non-fatal log) and nothing breaks — which is how the published Marketplace / Open VSX extension runs for end users. So the E2E suite no longer patches the test VS Code's `product.json`: - remove `test/e2e/enable-proposed-api.js` - remove the `setup:e2e:proposed-api` script and drop it from `setup:e2e` - update the plan doc accordingly (§0, §6.x, §8, §9) Verified: `npm run test:e2e` passes with no Deepnote entry in the test VS Code's `product.json` allow-list, including a clean-state run that builds the venv from scratch. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01LQk7n13UfmeDo2ujy8X4LX --- package.json | 3 +- specs/e2e-extester-testing-plan.md | 136 +++++------------------------ test/e2e/enable-proposed-api.js | 81 ----------------- 3 files changed, 22 insertions(+), 198 deletions(-) delete mode 100644 test/e2e/enable-proposed-api.js diff --git a/package.json b/package.json index dbdd0573ec..0c066bec1d 100644 --- a/package.json +++ b/package.json @@ -2676,8 +2676,7 @@ "pretest:e2e": "npm run compile-e2e", "setup:e2e:vscode": "extest get-vscode -c max && extest get-chromedriver -c max", "setup:e2e:deps": "extest install-from-marketplace ms-python.python -e .test-extensions", - "setup:e2e:proposed-api": "node ./test/e2e/enable-proposed-api.js", - "setup:e2e": "npm run setup:e2e:vscode && npm run setup:e2e:deps && npm run setup:e2e:proposed-api", + "setup:e2e": "npm run setup:e2e:vscode && npm run setup:e2e:deps", "test:e2e": "extest setup-and-run \"./out/e2e/suite/*.e2e.test.js\" -c max -o ./test/e2e/settings.json -e .test-extensions -m ./test/e2e/.mocharc.js -i", "test:unittests": "mocha --config ./build/.mocha.unittests.js.json ./out/**/*.unit.test.js", "test": "npm run test:unittests", diff --git a/specs/e2e-extester-testing-plan.md b/specs/e2e-extester-testing-plan.md index 650da02965..88c1eb31d6 100644 --- a/specs/e2e-extester-testing-plan.md +++ b/specs/e2e-extester-testing-plan.md @@ -33,13 +33,15 @@ sensibly and so the setup is reproducible. - **A venv-capable Python interpreter** — env creation runs `python -m venv` (needs `ensurepip`) then `pip install deepnote-toolkit[server] ipykernel python-lsp-server deepnote-cli` (needs network). On Ubuntu: `python3.12-venv python3-pip`. -- **Proposed-API allow-listing.** The extension declares `enabledApiProposals` (notebook - kernel/execution APIs). VS Code blocks those for a normally-installed VSIX, and ExTester's - `setup-and-run` exposes no `--enable-proposed-api` flag, so we write the extension into the - downloaded VS Code's `product.json` `extensionEnabledApiProposals` allow-list — the same - mechanism stable VS Code uses for Microsoft extensions. Done by `test/e2e/enable-proposed-api.js` - (§6.8), wired as `npm run setup:e2e:proposed-api`. **Without this the extension never - activates** and the notebook stays empty. +- **No proposed-API allow-listing is needed.** The extension declares `enabledApiProposals` + (Jupyter's notebook kernel/execution set, inherited from the vscode-jupyter fork), and on a + plain stable VS Code those proposals are *not* granted — VS Code logs a non-fatal "CANNOT USE + these API proposals" line. That does **not** block anything: activation, the notebook + serializer, kernel execution, and output rendering all run on **stable** APIs (output goes + through `controller.createNotebookCellExecution` with a `replaceCells` fallback), and the few + genuinely-proposed calls are guarded (e.g. `if (!controller.createNotebookExecution)`). The + suite is verified passing on a plain stable VS Code with **no** `product.json` patch — which is + also how the published Marketplace/Open VSX extension runs for end users. - **`.gitignore` is not `.vscodeignore`.** `vsce` packs from `.vscodeignore`; the e2e `.test-extensions/` dir (~200 MB) must be excluded there too or every run packages a ~300 MB VSIX. (§6.2) @@ -82,7 +84,6 @@ sensibly and so the setup is reproducible. - 6.5 [`test/e2e/settings.json`](#65-teste2esettingsjson) - 6.6 [`test/e2e/fixtures/hello-world.deepnote`](#66-teste2efixtureshello-worlddeepnote) - 6.7 [`test/e2e/suite/helloWorld.e2e.test.ts`](#67-teste2esuitehelloworlde2etestts) - - 6.8 [`test/e2e/enable-proposed-api.js`](#68-teste2eenable-proposed-apijs) 7. [How the hard parts work](#7-how-the-hard-parts-work) - 7.1 [Reading rendered output from nested iframes](#71-reading-rendered-output-from-nested-iframes) - 7.2 [Driving QuickPicks & InputBoxes](#72-driving-quickpicks--inputboxes) @@ -102,8 +103,7 @@ sensibly and so the setup is reproducible. ```bash # one-time / when the extension changes npm run compile # build the extension under test → dist/extension.node.js -npm run setup:e2e # download test VS Code + ChromeDriver, install ms-python.python, - # and allow-list the extension's proposed APIs in product.json +npm run setup:e2e # download test VS Code + ChromeDriver and install ms-python.python # run the E2E suite (pretest compiles test/e2e → out/e2e, then extest packages & runs) npm run test:e2e @@ -112,9 +112,9 @@ npm run test:e2e xvfb-run --auto-servernum --server-args='-screen 0 1920x1080x24' npm run test:e2e ``` -`npm run setup:e2e` is the umbrella for `setup:e2e:vscode` → `setup:e2e:deps` → -`setup:e2e:proposed-api` (in that order — the proposed-API patch needs VS Code already -downloaded). Re-running it is safe (idempotent). +`npm run setup:e2e` is the umbrella for `setup:e2e:vscode` → `setup:e2e:deps` (in that order — +installing the Python extension uses the downloaded VS Code's CLI). Re-running it is safe. No +proposed-API patch is needed (§0). Prerequisites: the **system libraries + Xvfb** and a **venv-capable Python interpreter** from §0, and **network access** (creating the environment installs the Deepnote toolkit; the first @@ -248,7 +248,6 @@ A minimal one-notebook, one-code-block file is in §6.6. | `test/e2e/.mocharc.js` | new | UI-test timeouts/retries/reporter | | `test/e2e/settings.json` | new | VS Code user settings for the test instance | | `test/e2e/fixtures/hello-world.deepnote` | new | the one-notebook hello-world fixture | -| `test/e2e/enable-proposed-api.js` | new | allow-lists proposed APIs in the test VS Code's `product.json` (§0, §6.8) | | `test/e2e/suite/helloWorld.e2e.test.ts` | new | the single E2E test | Resulting layout: @@ -292,16 +291,13 @@ Add these scripts (placed alongside the other `test:*` scripts): "pretest:e2e": "npm run compile-e2e", "setup:e2e:vscode": "extest get-vscode -c max && extest get-chromedriver -c max", "setup:e2e:deps": "extest install-from-marketplace ms-python.python -e .test-extensions", - "setup:e2e:proposed-api": "node ./test/e2e/enable-proposed-api.js", - "setup:e2e": "npm run setup:e2e:vscode && npm run setup:e2e:deps && npm run setup:e2e:proposed-api", + "setup:e2e": "npm run setup:e2e:vscode && npm run setup:e2e:deps", "test:e2e": "extest setup-and-run \"./out/e2e/suite/*.e2e.test.js\" -c max -o ./test/e2e/settings.json -e .test-extensions -m ./test/e2e/.mocharc.js -i" } ``` `setup:e2e:deps` installs the Python extension using the *downloaded* test VS Code's CLI, so -`setup:e2e:vscode` must run first; `setup:e2e:proposed-api` patches that VS Code's -`product.json`, which `setup-and-run` then reuses from cache (it does not re-extract a cached -VS Code, so the patch survives). +`setup:e2e:vscode` must run first. What the `extest setup-and-run` flags mean: @@ -868,96 +864,6 @@ describe('Deepnote E2E — run "hello world"', function () { }); ``` -### 6.8 `test/e2e/enable-proposed-api.js` - -New file: allow-lists the extension's proposed APIs in the downloaded VS Code's -`product.json` (see §0). Run via `npm run setup:e2e:proposed-api` after the test VS Code -has been downloaded; idempotent. - -```js -// Enables the extension's proposed VS Code APIs in the ExTester-managed VS Code instance. -// -// The Deepnote extension declares `enabledApiProposals` in package.json (notebook kernel / -// execution APIs, etc.). VS Code blocks proposed APIs for a normally-installed extension unless -// it is launched in extension-development mode or with `--enable-proposed-api`, neither of which -// ExTester's `setup-and-run` exposes. The supported, black-box-friendly alternative is the -// `extensionEnabledApiProposals` allowlist in the downloaded VS Code's `product.json` — the very -// mechanism stable VS Code uses to allow Microsoft extensions (e.g. ms-python.python) to use -// proposed APIs. This script writes our extension into that allowlist, idempotently. -// -// It must run AFTER VS Code has been downloaded (`extest get-vscode`) and is safe to re-run. -// ExTester caches VS Code under `os.tmpdir()/test-resources` (override with TEST_RESOURCES), and -// `setup-and-run` does not re-extract a cached VS Code, so the patch survives subsequent runs. - -const fs = require('fs'); -const os = require('os'); -const path = require('path'); - -const extensionManifest = require('../../package.json'); - -function getStorageFolder() { - return process.env.TEST_RESOURCES ? process.env.TEST_RESOURCES : path.join(os.tmpdir(), 'test-resources'); -} - -// Locate `*/resources/app/product.json` under the storage folder across platforms -// (VSCode-linux-x64, VSCode-win32-x64, "Visual Studio Code.app/Contents", …). -function findProductJson(storageFolder) { - if (!fs.existsSync(storageFolder)) { - return undefined; - } - - for (const entry of fs.readdirSync(storageFolder)) { - const base = path.join(storageFolder, entry); - - for (const candidate of [ - path.join(base, 'resources', 'app', 'product.json'), - path.join(base, 'Contents', 'Resources', 'app', 'product.json') - ]) { - if (fs.existsSync(candidate)) { - return candidate; - } - } - } - - return undefined; -} - -function main() { - const proposals = extensionManifest.enabledApiProposals; - if (!Array.isArray(proposals) || proposals.length === 0) { - console.log('No enabledApiProposals declared in package.json; nothing to do.'); - return; - } - - const storageFolder = getStorageFolder(); - const productJsonPath = findProductJson(storageFolder); - if (!productJsonPath) { - console.error( - `Could not find a VS Code product.json under ${storageFolder}. ` + - `Download VS Code first (e.g. \`npm run setup:e2e:vscode\`), then re-run this script.` - ); - process.exit(1); - } - - const product = JSON.parse(fs.readFileSync(productJsonPath, 'utf8')); - product.extensionEnabledApiProposals = product.extensionEnabledApiProposals || {}; - - // Match on the canonical id and its lowercase form: VS Code compares extension ids - // case-insensitively (ExtensionIdentifier.toKey lowercases), and our publisher is capitalized. - const extensionId = `${extensionManifest.publisher}.${extensionManifest.name}`; - for (const id of new Set([extensionId, extensionId.toLowerCase()])) { - product.extensionEnabledApiProposals[id] = proposals; - } - - fs.writeFileSync(productJsonPath, `${JSON.stringify(product, null, '\t')}\n`); - - console.log(`Enabled proposed APIs for ${extensionId} in ${productJsonPath}`); - console.log(`Proposals: ${proposals.join(', ')}`); -} - -main(); -``` - --- ## 7. How the hard parts work @@ -1016,8 +922,9 @@ Flakiness guards built into the test: interpreters found"* and returns (no quick pick). The test detects the missing quick pick (`tryOpenInputBox` times out), dismisses notifications, waits, and **retries up to 6 times**. -- **Unique environment name per run** (`E2E Hello Env `) so a leftover env from - a Mocha retry can't trip the "name already exists" guard. +- **Idempotent environment creation** with a stable name (`E2E Hello Env`): `createEnvironment` + treats the "name already exists" guard as success, so a leftover env from a Mocha retry (or a + persistent local instance) is reused rather than colliding — and its venv is reused too. - **Notification waits are best-effort** for the transient success toasts; the authoritative gate is the rendered output, so an auto-dismissed toast never fails the test. - **`driver.wait(...)`** is used for every asynchronous UI state instead of bare sleeps @@ -1032,8 +939,7 @@ real VS Code window. First-time sequence: ```bash npm run compile # build the extension under test (produces dist/extension.node.js) -npm run setup:e2e # download test VS Code + ChromeDriver, install ms-python.python, - # and allow-list proposed APIs (§0) — one-time, idempotent +npm run setup:e2e # download test VS Code + ChromeDriver and install ms-python.python (one-time) npm run test:e2e # pretest compiles test/e2e → out/e2e, then extest packages & runs ``` @@ -1086,8 +992,8 @@ e2e: libxcomposite1 libxdamage1 libxrandr2 libxfixes3 libxext6 libxrender1 libpango-1.0-0 \ libcairo2 libatspi2.0-0 libx11-xcb1 libxcb-dri3-0 libxtst6 libsecret-1-0 \ libgssapi-krb5-2 libdbus-1-3 libexpat1 python3.12-venv python3-pip - # Download the test VS Code, install ms-python.python, and allow-list proposed APIs (§0). - # ExTester always launches with --no-sandbox, so no AppArmor sysctl is required. + # Download the test VS Code and install ms-python.python (§0). ExTester always launches with + # --no-sandbox, so no AppArmor sysctl is required; no proposed-API patch is needed either. - run: npm run setup:e2e - name: Run E2E uses: nick-fields/retry@v4 # absorb transient UI flakiness diff --git a/test/e2e/enable-proposed-api.js b/test/e2e/enable-proposed-api.js deleted file mode 100644 index 60e996f9ca..0000000000 --- a/test/e2e/enable-proposed-api.js +++ /dev/null @@ -1,81 +0,0 @@ -// Enables the extension's proposed VS Code APIs in the ExTester-managed VS Code instance. -// -// The Deepnote extension declares `enabledApiProposals` in package.json (notebook kernel / -// execution APIs, etc.). VS Code blocks proposed APIs for a normally-installed extension unless -// it is launched in extension-development mode or with `--enable-proposed-api`, neither of which -// ExTester's `setup-and-run` exposes. The supported, black-box-friendly alternative is the -// `extensionEnabledApiProposals` allowlist in the downloaded VS Code's `product.json` — the very -// mechanism stable VS Code uses to allow Microsoft extensions (e.g. ms-python.python) to use -// proposed APIs. This script writes our extension into that allowlist, idempotently. -// -// It must run AFTER VS Code has been downloaded (`extest get-vscode`) and is safe to re-run. -// ExTester caches VS Code under `os.tmpdir()/test-resources` (override with TEST_RESOURCES), and -// `setup-and-run` does not re-extract a cached VS Code, so the patch survives subsequent runs. - -const fs = require('fs'); -const os = require('os'); -const path = require('path'); - -const extensionManifest = require('../../package.json'); - -function getStorageFolder() { - return process.env.TEST_RESOURCES ? process.env.TEST_RESOURCES : path.join(os.tmpdir(), 'test-resources'); -} - -// Locate `*/resources/app/product.json` under the storage folder across platforms -// (VSCode-linux-x64, VSCode-win32-x64, "Visual Studio Code.app/Contents", …). -function findProductJson(storageFolder) { - if (!fs.existsSync(storageFolder)) { - return undefined; - } - - for (const entry of fs.readdirSync(storageFolder)) { - const base = path.join(storageFolder, entry); - - for (const candidate of [ - path.join(base, 'resources', 'app', 'product.json'), - path.join(base, 'Contents', 'Resources', 'app', 'product.json') - ]) { - if (fs.existsSync(candidate)) { - return candidate; - } - } - } - - return undefined; -} - -function main() { - const proposals = extensionManifest.enabledApiProposals; - if (!Array.isArray(proposals) || proposals.length === 0) { - console.log('No enabledApiProposals declared in package.json; nothing to do.'); - return; - } - - const storageFolder = getStorageFolder(); - const productJsonPath = findProductJson(storageFolder); - if (!productJsonPath) { - console.error( - `Could not find a VS Code product.json under ${storageFolder}. ` + - `Download VS Code first (e.g. \`npm run setup:e2e:vscode\`), then re-run this script.` - ); - process.exit(1); - } - - const product = JSON.parse(fs.readFileSync(productJsonPath, 'utf8')); - product.extensionEnabledApiProposals = product.extensionEnabledApiProposals || {}; - - // Match on the canonical id and its lowercase form: VS Code compares extension ids - // case-insensitively (ExtensionIdentifier.toKey lowercases), and our publisher is capitalized. - const extensionId = `${extensionManifest.publisher}.${extensionManifest.name}`; - for (const id of new Set([extensionId, extensionId.toLowerCase()])) { - product.extensionEnabledApiProposals[id] = proposals; - } - - fs.writeFileSync(productJsonPath, `${JSON.stringify(product, null, '\t')}\n`); - - console.log(`Enabled proposed APIs for ${extensionId} in ${productJsonPath}`); - console.log(`Proposals: ${proposals.join(', ')}`); -} - -main(); From af650f338a34c1955a47160d3d596794444d1313 Mon Sep 17 00:00:00 2001 From: tomas Date: Thu, 25 Jun 2026 10:04:31 +0000 Subject: [PATCH 3/7] chore(e2e): drop the pretest:e2e hook in favor of compile-e2e-watch Mirror the unit-test convention (compile-tsc / compile-tsc-watch then test:unittests): compile the test sources explicitly via `compile-e2e` (or `compile-e2e-watch` while iterating) instead of auto-compiling through a `pretest:e2e` npm lifecycle hook. `test:e2e` now just runs the already-compiled suite under out/e2e. Updates the plan doc to match. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01LQk7n13UfmeDo2ujy8X4LX --- package.json | 2 +- specs/e2e-extester-testing-plan.md | 20 +++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 0c066bec1d..36f75b4555 100644 --- a/package.json +++ b/package.json @@ -2673,7 +2673,7 @@ "test:performance:notebook": "cross-env VSC_JUPYTER_CI_TEST_GREP=@notebookPerformance VSC_JUPYTER_CI_TEST_DO_NOT_INSTALL_PYTHON_EXT=true CODE_TESTS_WORKSPACE=src/test/datascience TEST_FILES_SUFFIX=*.vscode.test,*.vscode.common.test node ./out/test/standardTest.node.js", "test:smoke": "cross-env VSC_JUPYTER_FORCE_LOGGING=true node --no-force-async-hooks-checks ./out/test/smokeTest.node.js", "compile-e2e": "tsc -p ./test/e2e/tsconfig.json", - "pretest:e2e": "npm run compile-e2e", + "compile-e2e-watch": "tsc -p ./test/e2e/tsconfig.json --watch", "setup:e2e:vscode": "extest get-vscode -c max && extest get-chromedriver -c max", "setup:e2e:deps": "extest install-from-marketplace ms-python.python -e .test-extensions", "setup:e2e": "npm run setup:e2e:vscode && npm run setup:e2e:deps", diff --git a/specs/e2e-extester-testing-plan.md b/specs/e2e-extester-testing-plan.md index 88c1eb31d6..92312e533d 100644 --- a/specs/e2e-extester-testing-plan.md +++ b/specs/e2e-extester-testing-plan.md @@ -105,7 +105,10 @@ sensibly and so the setup is reproducible. npm run compile # build the extension under test → dist/extension.node.js npm run setup:e2e # download test VS Code + ChromeDriver and install ms-python.python -# run the E2E suite (pretest compiles test/e2e → out/e2e, then extest packages & runs) +# compile the test sources (test/e2e → out/e2e), or run compile-e2e-watch while iterating +npm run compile-e2e + +# run the E2E suite (extest packages the extension, downloads/launches VS Code, runs the tests) npm run test:e2e # headless Linux (CI or a server without a display): wrap in a virtual framebuffer @@ -288,7 +291,7 @@ Add these scripts (placed alongside the other `test:*` scripts): "scripts": { // …existing… "compile-e2e": "tsc -p ./test/e2e/tsconfig.json", - "pretest:e2e": "npm run compile-e2e", + "compile-e2e-watch": "tsc -p ./test/e2e/tsconfig.json --watch", "setup:e2e:vscode": "extest get-vscode -c max && extest get-chromedriver -c max", "setup:e2e:deps": "extest install-from-marketplace ms-python.python -e .test-extensions", "setup:e2e": "npm run setup:e2e:vscode && npm run setup:e2e:deps", @@ -296,8 +299,10 @@ Add these scripts (placed alongside the other `test:*` scripts): } ``` -`setup:e2e:deps` installs the Python extension using the *downloaded* test VS Code's CLI, so -`setup:e2e:vscode` must run first. +`compile-e2e` builds `test/e2e` → `out/e2e` (run it, or `compile-e2e-watch`, before `test:e2e`) — +mirroring the unit-test flow (`compile-tsc` / `compile-tsc-watch` then `test:unittests`), with no +`pre`-hook coupling compilation to the run. `setup:e2e:deps` installs the Python extension using +the *downloaded* test VS Code's CLI, so `setup:e2e:vscode` must run first. What the `extest setup-and-run` flags mean: @@ -938,9 +943,10 @@ Flakiness guards built into the test: real VS Code window. First-time sequence: ```bash -npm run compile # build the extension under test (produces dist/extension.node.js) -npm run setup:e2e # download test VS Code + ChromeDriver and install ms-python.python (one-time) -npm run test:e2e # pretest compiles test/e2e → out/e2e, then extest packages & runs +npm run compile # build the extension under test (produces dist/extension.node.js) +npm run setup:e2e # download test VS Code + ChromeDriver and install ms-python.python (one-time) +npm run compile-e2e # build the test sources (test/e2e → out/e2e); or run compile-e2e-watch +npm run test:e2e # extest packages the extension, downloads/launches VS Code, runs the tests ``` - **Linux is headless** → install the Electron/Chromium system libraries and Xvfb (§0), then From 8e4f5890f34efb44b2bab0b373cdd84429c06f35 Mon Sep 17 00:00:00 2001 From: tomas Date: Thu, 25 Jun 2026 12:35:54 +0000 Subject: [PATCH 4/7] chore(e2e): fix Check Licenses and Package Lock Drift for the e2e deps vscode-extension-tester's transitive deps tripped two CI gates: - Check Licenses: allow WTFPL (@azu/style-format) and Artistic-2.0 (binaryextensions/editions/istextorbinary/textextensions/version-range), and exclude the proprietary Microsoft-licensed @vscode/vsce-sign* binaries via --excludePackagesStartingWith. - Package Lock Drift: regenerate package-lock.json so `npm install` is a no-op; typed-rest-client's requires.qs had the resolved "6.15.2" pinned instead of its declared range "^6.9.1", which npm install kept rewriting. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01LQk7n13UfmeDo2ujy8X4LX --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8ce5859a48..0c20bf31e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58489,7 +58489,7 @@ "integrity": "sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==", "dev": true, "requires": { - "qs": "6.15.2", + "qs": "^6.9.1", "tunnel": "0.0.6", "underscore": "^1.12.1" } diff --git a/package.json b/package.json index 36f75b4555..fb1ffcff2e 100644 --- a/package.json +++ b/package.json @@ -2636,7 +2636,7 @@ "build:prerelease": "cross-env IS_PRE_RELEASE_VERSION_OF_JUPYTER_EXTENSION=true npm run build", "build:stable": "cross-env IS_PRE_RELEASE_VERSION_OF_JUPYTER_EXTENSION=false npm run build", "build": "concurrently npm:compile-release npm:updatePackageJsonForBundle", - "check-licenses": "npx license-checker-rseidelsohn --onlyAllow 'MIT;Apache-2.0;Apache v2;ISC;BSD;BSD-2-Clause;BSD-3-Clause;0BSD;Python-2.0;CC0-1.0;CC-BY-3.0;CC-BY-4.0;Unlicense;BlueOak-1.0.0;MPL-2.0' --excludePrivatePackages --excludePackages 'bootstrap-less@3.3.8;chai-as-promised@7.1.1;esbuild-plugin-less@1.3.19;eslint-plugin-local-rules@1.0.0;truncate-utf8-bytes@1.0.2;utf8-byte-length@1.0.4;@cspell/dict-en-common-misspellings@2.1.6'", + "check-licenses": "npx license-checker-rseidelsohn --onlyAllow 'MIT;Apache-2.0;Apache v2;ISC;BSD;BSD-2-Clause;BSD-3-Clause;0BSD;Python-2.0;CC0-1.0;CC-BY-3.0;CC-BY-4.0;Unlicense;BlueOak-1.0.0;MPL-2.0;WTFPL;Artistic-2.0' --excludePrivatePackages --excludePackages 'bootstrap-less@3.3.8;chai-as-promised@7.1.1;esbuild-plugin-less@1.3.19;eslint-plugin-local-rules@1.0.0;truncate-utf8-bytes@1.0.2;utf8-byte-length@1.0.4;@cspell/dict-en-common-misspellings@2.1.6' --excludePackagesStartingWith '@vscode/vsce-sign'", "checkDependencies": "gulp checkDependencies", "clean": "gulp clean", "compile-esbuild-watch": "npx tsx build/esbuild/build.ts --watch", From 55911dc9c0a4ea5485d0c585b7b961da324474a4 Mon Sep 17 00:00:00 2001 From: tomas Date: Thu, 25 Jun 2026 12:41:48 +0000 Subject: [PATCH 5/7] chore(e2e): stop tracking the e2e testing plan, keep it local-only Remove specs/e2e-extester-testing-plan.md from version control while keeping the working-tree copy on disk (now untracked). .gitignore is intentionally left unchanged. The E2E suite under test/e2e/ does not depend on this doc. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01LQk7n13UfmeDo2ujy8X4LX --- specs/e2e-extester-testing-plan.md | 1162 ---------------------------- 1 file changed, 1162 deletions(-) delete mode 100644 specs/e2e-extester-testing-plan.md diff --git a/specs/e2e-extester-testing-plan.md b/specs/e2e-extester-testing-plan.md deleted file mode 100644 index 92312e533d..0000000000 --- a/specs/e2e-extester-testing-plan.md +++ /dev/null @@ -1,1162 +0,0 @@ -# E2E Testing with ExTester (vscode-extension-tester) — Self-Contained Plan & Reference - -> **What this document is.** A single, self-contained guide to the end-to-end (E2E) UI -> test layer for this extension, built on Red Hat's **ExTester** -> (`vscode-extension-tester`). It explains *why* and *how*, and embeds the **complete, -> verbatim contents of every file** involved, so you can understand, reproduce, run, and -> extend the setup from this document alone — without opening any other file. -> -> **Status:** implemented **and verified passing locally** on headless Linux (Ubuntu 24.04, -> Xvfb, VS Code 1.111.0). The files below exist in the repo at the stated paths. -> -> **The one test we ship** drives the full Deepnote happy path through the *real* VS Code -> UI: open a one-notebook `.deepnote` file containing `print("hello world")` → create a -> Deepnote environment → select it for the notebook → kernel connects → run the cell → -> assert the rendered output contains `hello world`. - ---- - -## 0. Implementation reality (read this first) - -Bringing this test green required several fixes beyond the first draft of the plan. They are -baked into the files reproduced below; this section explains the *why* so the test reads -sensibly and so the setup is reproducible. - -**Setup the headless run needs (one-time):** - -- **System libraries for Electron/Chromium** (the test VS Code won't launch without them). On - Ubuntu 24.04: `libatk1.0-0t64 libatk-bridge2.0-0t64 libcups2t64 libgtk-3-0t64 - libgdk-pixbuf-2.0-0 libgbm1 libasound2t64 libnss3 libnspr4 libxss1 libxshmfence1 libdrm2 - libxkbcommon0 libxcomposite1 libxdamage1 libxrandr2 libxfixes3 libxext6 libxrender1 - libpango-1.0-0 libcairo2 libatspi2.0-0 libx11-xcb1 libxcb-dri3-0 libxtst6 libsecret-1-0 - libgssapi-krb5-2 libdbus-1-3 libexpat1` plus `xvfb`. -- **A venv-capable Python interpreter** — env creation runs `python -m venv` (needs - `ensurepip`) then `pip install deepnote-toolkit[server] ipykernel python-lsp-server - deepnote-cli` (needs network). On Ubuntu: `python3.12-venv python3-pip`. -- **No proposed-API allow-listing is needed.** The extension declares `enabledApiProposals` - (Jupyter's notebook kernel/execution set, inherited from the vscode-jupyter fork), and on a - plain stable VS Code those proposals are *not* granted — VS Code logs a non-fatal "CANNOT USE - these API proposals" line. That does **not** block anything: activation, the notebook - serializer, kernel execution, and output rendering all run on **stable** APIs (output goes - through `controller.createNotebookCellExecution` with a `replaceCells` fallback), and the few - genuinely-proposed calls are guarded (e.g. `if (!controller.createNotebookExecution)`). The - suite is verified passing on a plain stable VS Code with **no** `product.json` patch — which is - also how the published Marketplace/Open VSX extension runs for end users. -- **`.gitignore` is not `.vscodeignore`.** `vsce` packs from `.vscodeignore`; the e2e - `.test-extensions/` dir (~200 MB) must be excluded there too or every run packages a - ~300 MB VSIX. (§6.2) - -**Why the test code looks the way it does (vs a naive script):** - -- **Open a workspace *folder*, not just the file.** The Deepnote serializer reads a "snapshot" - during deserialization and, with **no** workspace folder, blocks forever on - `await window.showWarningMessage('Cannot read snapshot: No workspace folders found.')` — - leaving the notebook blank. So the test opens the temp dir as a folder first, then the file. -- **ExTester's `openResources` (`code -r `) silently no-ops** in this sandboxed instance - (IPC reuse fails), so we drive the *running* window directly: the folder via the simple - "Open Folder" dialog, the notebook via Quick Open ("Go to File…"). -- **The simple "Open Folder" dialog's Enter navigates *into* a directory** rather than - accepting it — the deterministic accept is the dialog's **"OK" button**, which we click. The - open also reloads the window, so we wait for the old workbench element to detach. -- **`deepnote.runallcells` is gated** behind context keys (`deepnote.ispythonornativeactive`, - …) that aren't reliably set under automation, so `Workbench.executeCommand('Jupyter: Run All - Cells')` can miss and open the wrong view. We **click the notebook toolbar's "Run All" - button** instead, and **re-issue it periodically** because the first run can be dropped right - after the kernel connects. -- **Environment creation is idempotent** (treats "already exists" as success) with a stable - name, so retries — and persistent local instances — reuse the already-provisioned venv. - ---- - -## Table of contents - -0. [Implementation reality (read this first)](#0-implementation-reality-read-this-first) -1. [TL;DR — run it](#1-tldr--run-it) -2. [Background: what ExTester is and how it works](#2-background-what-extester-is-and-how-it-works) -3. [The Deepnote flow this test drives (verified against the code)](#3-the-deepnote-flow-this-test-drives-verified-against-the-code) -4. [Design decisions](#4-design-decisions) -5. [File manifest](#5-file-manifest) -6. [Complete file contents (verbatim)](#6-complete-file-contents-verbatim) - - 6.1 [`package.json` additions](#61-packagejson-additions) - - 6.2 [`.gitignore` additions](#62-gitignore-additions) - - 6.3 [`test/e2e/tsconfig.json`](#63-teste2etsconfigjson) - - 6.4 [`test/e2e/.mocharc.js`](#64-teste2emocharcjs) - - 6.5 [`test/e2e/settings.json`](#65-teste2esettingsjson) - - 6.6 [`test/e2e/fixtures/hello-world.deepnote`](#66-teste2efixtureshello-worlddeepnote) - - 6.7 [`test/e2e/suite/helloWorld.e2e.test.ts`](#67-teste2esuitehelloworlde2etestts) -7. [How the hard parts work](#7-how-the-hard-parts-work) - - 7.1 [Reading rendered output from nested iframes](#71-reading-rendered-output-from-nested-iframes) - - 7.2 [Driving QuickPicks & InputBoxes](#72-driving-quickpicks--inputboxes) -8. [Running it](#8-running-it) -9. [CI integration](#9-ci-integration) -10. [Gotchas & flakiness mitigation](#10-gotchas--flakiness-mitigation) -11. [Where ExTester fits vs the other test layers](#11-where-extester-fits-vs-the-other-test-layers) -12. [Risks & mitigations](#12-risks--mitigations) -13. [Appendix A — ExTester API surface used](#appendix-a--extester-api-surface-used) -14. [Appendix B — Deepnote command-id reference](#appendix-b--deepnote-command-id-reference) -15. [References](#references) - ---- - -## 1. TL;DR — run it - -```bash -# one-time / when the extension changes -npm run compile # build the extension under test → dist/extension.node.js -npm run setup:e2e # download test VS Code + ChromeDriver and install ms-python.python - -# compile the test sources (test/e2e → out/e2e), or run compile-e2e-watch while iterating -npm run compile-e2e - -# run the E2E suite (extest packages the extension, downloads/launches VS Code, runs the tests) -npm run test:e2e - -# headless Linux (CI or a server without a display): wrap in a virtual framebuffer -xvfb-run --auto-servernum --server-args='-screen 0 1920x1080x24' npm run test:e2e -``` - -`npm run setup:e2e` is the umbrella for `setup:e2e:vscode` → `setup:e2e:deps` (in that order — -installing the Python extension uses the downloaded VS Code's CLI). Re-running it is safe. No -proposed-API patch is needed (§0). - -Prerequisites: the **system libraries + Xvfb** and a **venv-capable Python interpreter** from -§0, and **network access** (creating the environment installs the Deepnote toolkit; the first -kernel start can take minutes). - ---- - -## 2. Background: what ExTester is and how it works - -**ExTester** (`vscode-extension-tester`, current `^8.23.0`) is a UI-testing framework for -VS Code extensions built on **Selenium WebDriver**. It drives the *real* VS Code desktop -app (Electron/Chromium) as if it were a browser — clicking buttons, opening the command -palette, typing into editors, reading notifications, and inspecting the DOM — exercising -the extension exactly as a user would. Red Hat created it to UI-test their own extensions -(vscode-java, vscode-quarkus, vscode-server-connector, …) and it is the de-facto standard -for VS Code extension UI testing. - -The `extest` CLI does five things: - -1. Downloads a clean, pinned **VS Code** test instance. -2. Downloads the **ChromeDriver** matching that VS Code's Chromium. -3. Loads our extension into it (from source by default; from a `.vsix` with `-u`). -4. Launches VS Code under WebDriver and runs our **Mocha** test files. -5. Auto-screenshots failing tests into `/_screenshots`. - -``` -Mocha test → ExTester Page Objects → selenium-webdriver → ChromeDriver → VS Code (+ our extension) -``` - -We write tests against ExTester's **Page Object API** (`Workbench`, `EditorView`, -`InputBox`, `Notification`, `WebView`, …) — all imported from the single -`vscode-extension-tester` package — instead of hand-writing DOM selectors. - -**Key facts that shape this plan** (from the ExTester docs + studying Red Hat's own repos): - -- **No headless flag.** VS Code is a real GUI app; on Linux you run "headed" inside - `xvfb`. (§8, §9) -- **WebViews are iframes.** Anything rendered in a webview (including **notebook cell - output**) requires switching the WebDriver context into the iframe and back. (§7.1) -- **No notebook page object exists.** ExTester has `TextEditor`, `WebView`, - `CustomEditor`, … but nothing notebook-specific. We use `WebView` — its frame-switching - happens to descend exactly the two iframe levels the notebook output uses. (§7.1) -- **chai v4 + CommonJS.** Every current Red Hat repo uses chai v4 with CommonJS; chai v5 - is ESM-only and adds friction. This repo already ships chai `^4.3.10`. (§4) -- **Compatibility is a floating window.** ExTester officially supports the latest ~3 VS - Code minors (`-c min|max`), oldest workable is 1.37.0; use the newest ExTester for the - newest VS Code. Node = active LTS. (§4) -- **Reliability comes from `driver.wait(...)`,** preferring `Workbench.executeCommand` - over clicking menus, always `switchBack()` in `finally`, and bumping Mocha timeouts well - above the unusable 2 s default. (§10) - ---- - -## 3. The Deepnote flow this test drives (verified against the code) - -Each step below was confirmed by reading the extension source (anchors given for -maintainers). - -1. **Open `.deepnote` → native Notebook editor.** The extension registers a serializer for - notebook type **`deepnote`** (`package.json` → `contributes.notebooks[].type = "deepnote"`, - selector `*.deepnote`; `DeepnoteNotebookSerializer` in - `src/notebooks/deepnote/deepnoteSerializer.ts`; registered in - `deepnoteActivationService.ts`). Opening the raw file works without the explorer: a - single-notebook file resolves via `findDefaultNotebook()` (serializer line ~103). - Before an environment is chosen, the notebook gets a **placeholder controller** labeled - **"Deepnote: Select Environment"** (`deepnoteKernelAutoSelector.node.ts` → - `createPlaceholderController`). - -2. **Create an environment** → command **`deepnote.environments.create`** (palette label - **"Deepnote: Create Environment"**; `deepnoteEnvironmentsView.node.ts` → - `createEnvironmentCommand`). It prompts, in order: - - a **QuickPick of Python interpreters** (from the Python extension API - `api.environments.known`; placeholder *"Select a Python interpreter for this - environment"*) — if none are discovered yet it shows *"No Python interpreters found"* - and returns; - - an **input box for the name**; - - an **input box for packages** (optional — empty + Enter is valid); - - an **input box for description** (optional); - - then a progress notification *"Creating environment …"* and finally - *"Environment "…" created successfully!"*. - -3. **Select it for the notebook** → command **`deepnote.environments.selectForNotebook`** - (palette label **"Deepnote: Select Environment for Notebook"**; requires the active - editor to be a `deepnote` notebook). It shows a **QuickPick of environments** (plus - *"$(add) Create New Environment"*). Choosing one calls - `kernelAutoSelector.rebuildController(notebook, …)` inside a *"Switching to - environment…"* progress notification, ending with *"Environment switched - successfully"*. - -4. **Kernel connects.** `rebuildController` → `ensureKernelSelectedWithConfiguration` → - `ensureControllerSelectedForNotebook` provisions the venv/toolkit, **explicitly selects - the controller** via `commands.executeCommand('notebook.selectKernel', { … id: controller.connection.id … })` - (auto-selector line ~740), and **disposes the placeholder** (line ~502). After this the - real controller is the selected kernel, so "Run All" executes through it. - -5. **Execute** → command **`deepnote.runallcells`** (palette label **"Jupyter: Run All - Cells"**), which runs the cell through the selected controller, starting the kernel. - -6. **Validate output** → the rendered stdout `hello world` appears in the notebook - **output webview** (nested iframes — §7.1). - -### The fixture format - -Confirmed from `src/notebooks/deepnote/deepnoteSerializer.unit.test.ts` and `@deepnote/blocks`. -A minimal one-notebook, one-code-block file is in §6.6. - ---- - -## 4. Design decisions - -| Decision | Choice | Why | -| --- | --- | --- | -| Where tests live | `test/e2e/` (top-level, **outside `src/`**) | The root `tsconfig.json` only compiles `./src/**/*` and esbuild only bundles `src`, so E2E code never enters the extension bundle, the unit glob (`out/**/*.unit.test.js`), or the integration glob (`*.vscode.test`). Zero interference. | -| Compilation | Dedicated `test/e2e/tsconfig.json` → `out/e2e/` | Isolated from the strict extension config; `out/` is already gitignored, so compiled tests and `_screenshots` are ignored too. | -| Module system | **CommonJS + chai v4** | Matches every current Red Hat repo and the repo's existing chai `^4.3.10`; avoids chai v5 ESM friction. | -| New dependency | only `vscode-extension-tester@^8.23.0` | It transitively brings selenium-webdriver, page-objects, locators, @vscode/vsce, c8. `mocha` (`^11`), `chai`/`@types/chai` (`^4`), `@types/mocha` (`^10`) already exist and are reused. | -| VS Code version | `-c max` | Best DOM-locator match for ExTester 8.23 and most reliable automation; still within our `engines.vscode` `^1.95.0`. Swap to `-c 1.95.0` to additionally validate the minimum supported VS Code. | -| Output validation | read the **output iframe**, gated on `getViewToSwitchTo()` | Reading the whole document body would falsely match the cell's *source* `print("hello world")` shown in the editor. Gating on a real output iframe + output-scoped selectors prevents that. (§7.1) | -| Fixture handling | copy to a temp dir, open the copy | Execution dirties the notebook; a throwaway copy keeps the committed fixture pristine and avoids save prompts. | - ---- - -## 5. File manifest - -| Path | New? | Purpose | -| --- | --- | --- | -| `package.json` | modified | adds `vscode-extension-tester` devDep + the e2e npm scripts | -| `.gitignore` | modified | ignores ExTester's `test-resources/` and `.test-extensions/` | -| `.vscodeignore` | modified | excludes `.test-extensions/` + `test-resources/` from the VSIX (§6.2) | -| `test/e2e/tsconfig.json` | new | isolated CommonJS compile → `out/e2e` | -| `test/e2e/.mocharc.js` | new | UI-test timeouts/retries/reporter | -| `test/e2e/settings.json` | new | VS Code user settings for the test instance | -| `test/e2e/fixtures/hello-world.deepnote` | new | the one-notebook hello-world fixture | -| `test/e2e/suite/helloWorld.e2e.test.ts` | new | the single E2E test | - -Resulting layout: - -``` -test/e2e/ -├── tsconfig.json -├── .mocharc.js -├── settings.json -├── fixtures/ -│ └── hello-world.deepnote -└── suite/ - └── helloWorld.e2e.test.ts -``` - -ExTester writes its downloads to `test-resources/` and installs extensions into -`.test-extensions/` (both gitignored). Compiled tests + failure screenshots live under -`out/e2e/` (already gitignored via `out`). - ---- - -## 6. Complete file contents (verbatim) - -### 6.1 `package.json` additions - -Add one dev dependency: - -```jsonc -"devDependencies": { - // …existing… - "vscode-extension-tester": "^8.23.0" -} -``` - -Add these scripts (placed alongside the other `test:*` scripts): - -```jsonc -"scripts": { - // …existing… - "compile-e2e": "tsc -p ./test/e2e/tsconfig.json", - "compile-e2e-watch": "tsc -p ./test/e2e/tsconfig.json --watch", - "setup:e2e:vscode": "extest get-vscode -c max && extest get-chromedriver -c max", - "setup:e2e:deps": "extest install-from-marketplace ms-python.python -e .test-extensions", - "setup:e2e": "npm run setup:e2e:vscode && npm run setup:e2e:deps", - "test:e2e": "extest setup-and-run \"./out/e2e/suite/*.e2e.test.js\" -c max -o ./test/e2e/settings.json -e .test-extensions -m ./test/e2e/.mocharc.js -i" -} -``` - -`compile-e2e` builds `test/e2e` → `out/e2e` (run it, or `compile-e2e-watch`, before `test:e2e`) — -mirroring the unit-test flow (`compile-tsc` / `compile-tsc-watch` then `test:unittests`), with no -`pre`-hook coupling compilation to the run. `setup:e2e:deps` installs the Python extension using -the *downloaded* test VS Code's CLI, so `setup:e2e:vscode` must run first. - -What the `extest setup-and-run` flags mean: - -- positional glob `"./out/e2e/suite/*.e2e.test.js"` — the compiled test(s) to run. -- `-c max` — download the newest VS Code ExTester supports (matching ChromeDriver fetched - automatically). -- `-o ./test/e2e/settings.json` — user settings applied to the test instance. -- `-e .test-extensions` — isolated extensions directory. -- `-m ./test/e2e/.mocharc.js` — the Mocha config. -- `-i` — install declared extension *dependencies* from the Marketplace. (We have none - declared, so the Python extension is installed separately via `setup:e2e:deps`, since it - is not an `extensionDependency`.) - -### 6.2 `.gitignore` additions - -```gitignore -# ExTester (vscode-extension-tester) E2E artifacts -test-resources -.test-extensions -``` - -(`out/` is already ignored, covering `out/e2e/` and any `_screenshots` beneath it.) - -**Also add to `.vscodeignore`** (separate from `.gitignore` — `vsce` reads `.vscodeignore` -when it exists and ignores `.gitignore` entirely). `test/` and `out/` are already excluded -there, but the e2e *artifact* dirs are not, and `-e .test-extensions` puts ~200 MB / 10k files -in the repo root that would otherwise be packed into the VSIX on every run: - -```gitignore -.test-extensions/** -test-resources/** -``` - -### 6.3 `test/e2e/tsconfig.json` - -```jsonc -{ - // Standalone config for the ExTester E2E suite. It is intentionally independent of the - // extension's root tsconfig (which only compiles ./src) so these tests never enter the - // esbuild bundle or the unit/integration test globs. CommonJS + chai v4 keeps - // `import { expect } from 'chai'` working without ESM friction. - "compilerOptions": { - "module": "commonjs", - "target": "ES2022", - "lib": ["ES2022", "DOM"], - "moduleResolution": "node", - "outDir": "../../out/e2e", - "rootDir": ".", - "sourceMap": true, - "strict": true, - "skipLibCheck": true, - "esModuleInterop": true, - "resolveJsonModule": true, - "types": ["node", "mocha", "chai"] - }, - "include": ["**/*.ts"] -} -``` - -### 6.4 `test/e2e/.mocharc.js` - -```js -// Mocha configuration for the ExTester (vscode-extension-tester) E2E suite. -// UI tests are slow: the 2s Mocha default is unusable. Individual waits inside the -// tests are the real guard rails; this is a generous suite-level safety net. -module.exports = { - timeout: 900000, // 15 min — env creation + first kernel start (venv + toolkit) can be slow - retries: 1, // absorb transient UI flakiness with a single retry - reporter: 'spec', - color: true -}; -``` - -### 6.5 `test/e2e/settings.json` - -Reduces UI noise and makes automation deterministic. The `files.simpleDialog.enable` + -`window.dialogStyle: custom` pair turns native OS dialogs (undriveable by Selenium) into -in-window quick inputs — a Red-Hat-wide best practice. `security.workspace.trust.enabled: -false` prevents a workspace-trust modal from blocking the run. - -```json -{ - "files.simpleDialog.enable": true, - "window.dialogStyle": "custom", - "workbench.editor.enablePreview": false, - "workbench.startupEditor": "none", - "extensions.ignoreRecommendations": true, - "workbench.remoteIndicator.showExtensionRecommendations": false, - "git.autoRepositoryDetection": false, - "telemetry.telemetryLevel": "off", - "update.mode": "none", - "jupyter.kernels.trusted": true, - "security.workspace.trust.enabled": false -} -``` - -### 6.6 `test/e2e/fixtures/hello-world.deepnote` - -```yaml -version: '1.0.0' -metadata: - createdAt: '2025-01-01T00:00:00.000Z' - modifiedAt: '2025-01-01T00:00:00.000Z' -project: - id: e2e-hello-world-project - name: E2E Hello World - notebooks: - - id: e2e-hello-world-notebook - name: Hello World - blocks: - - id: e2e-hello-block - blockGroup: e2e-group - type: code - content: |- - print("hello world") - sortingKey: a0 - metadata: {} - executionMode: block - isModule: false - settings: {} -``` - -### 6.7 `test/e2e/suite/helloWorld.e2e.test.ts` - -```ts -/** - * End-to-end UI test driven by ExTester (vscode-extension-tester). - * - * It exercises the full Deepnote happy path through the *real* VS Code UI: - * 1. open a one-notebook `.deepnote` file containing `print("hello world")` - * 2. create a Deepnote environment (command `deepnote.environments.create`) - * 3. select that environment for the notebook (command `deepnote.environments.selectForNotebook`) - * — this builds and selects the notebook's kernel controller ("kernel connected") - * 4. run the cell (the notebook toolbar's "Run All" button) - * 5. assert the rendered stdout output contains "hello world" - * - * Prerequisites (see specs/e2e-extester-testing-plan.md): - * - The Python extension (`ms-python.python`) must be installed in the test instance - * (`npm run setup:e2e:deps`) and at least one Python interpreter must be discoverable. - * - Creating the environment provisions a venv and the Deepnote toolkit, which needs - * network access; the first kernel start can take a few minutes. - * - * Notebook output in VS Code renders inside two nested iframes - * (iframe.webview.ready -> #active-frame). ExTester's WebView page object descends - * exactly those two levels, which is how we read the rendered output below. - */ - -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; - -import { expect } from 'chai'; -import { - By, - EditorView, - InputBox, - Notification, - VSBrowser, - WebView, - Workbench, - type WebDriver -} from 'vscode-extension-tester'; - -// Command palette labels (category + title) the way `Workbench.executeCommand` matches them. -const CREATE_ENV_COMMAND = 'Deepnote: Create Environment'; -const SELECT_ENV_COMMAND = 'Deepnote: Select Environment for Notebook'; - -const NOTEBOOK_FILE_NAME = 'hello-world.deepnote'; -const EXPECTED_OUTPUT = 'hello world'; - -// Timeouts (ms). UI ops are slow and the first kernel start is the slowest step. -const WORKBENCH_TIMEOUT = 60_000; -const QUICK_PICK_TIMEOUT = 30_000; -const ENV_CREATED_TIMEOUT = 120_000; -const KERNEL_CONNECT_TIMEOUT = 300_000; -const OUTPUT_TIMEOUT = 300_000; -// How often to re-issue "Run All" while waiting for output — the first run can be dropped right -// after the kernel connects. -const RUN_ALL_REISSUE_INTERVAL = 25_000; -const INTERPRETER_RETRY_DELAY = 5_000; -const MAX_CREATE_ATTEMPTS = 6; -// The in-window simple file/folder dialog needs a beat to resolve a typed path before it accepts. -const DIALOG_RESOLVE_DELAY = 1_500; -const FOLDER_OPEN_ATTEMPTS = 5; -const FOLDER_RELOAD_TIMEOUT = 12_000; - -// Selectors that only exist inside the notebook output iframe (`#active-frame`), -// so reading them cannot accidentally match the cell's source in the editor. -const OUTPUT_SELECTOR = '.output_container, .output, .rendered-output'; - -describe('Deepnote E2E — run "hello world"', function () { - // Per-test timeout for the whole suite (overrides the mocharc default for these tests). - this.timeout(22 * 60 * 1000); - - let driver: WebDriver; - let notebookFile: string; - // A stable name: createEnvironment is idempotent (it treats "already exists" as success), so a - // leftover environment from a previous or retried run is reused rather than colliding — which - // also lets a persistent test instance reuse the already-provisioned venv. - const environmentName = 'E2E Hello Env'; - - before(async function () { - driver = VSBrowser.instance.driver; - - // Open a throwaway copy so execution-dirtied notebook state never touches the source tree. - const source = path.resolve(process.cwd(), 'test', 'e2e', 'fixtures', NOTEBOOK_FILE_NAME); - const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'deepnote-e2e-')); - notebookFile = path.join(tempDir, NOTEBOOK_FILE_NAME); - fs.copyFileSync(source, notebookFile); - - await VSBrowser.instance.waitForWorkbench(WORKBENCH_TIMEOUT); - - // Open the temp directory as a workspace folder FIRST. The Deepnote serializer reads a - // "snapshot" during deserialization and, with no workspace folder open, blocks on a - // `showWarningMessage('Cannot read snapshot: No workspace folders found.')` that never - // resolves headlessly — leaving the notebook blank. A workspace folder also provides the - // requirements.txt path the kernel auto-selector needs. (Opening a folder reloads the - // window, so we re-wait for the workbench afterwards.) - await openFolderViaDialog(tempDir); - await VSBrowser.instance.waitForWorkbench(WORKBENCH_TIMEOUT); - - // Open the notebook by driving the running window directly. ExTester's `openResources` - // shells out to `code -r ` (reuse-window over IPC), which silently no-ops in a - // sandboxed/headless environment. Now that the containing folder is the workspace, the - // notebook is reachable by name through Quick Open ("Go to File..."). - await openWorkspaceFile(NOTEBOOK_FILE_NAME); - - // The native notebook editor opens because the extension registers a serializer for - // the `deepnote` notebook type; a single-notebook file resolves to its default notebook. - await driver.wait( - async () => (await new EditorView().getOpenEditorTitles()).some((t) => t.includes(NOTEBOOK_FILE_NAME)), - WORKBENCH_TIMEOUT, - 'Deepnote notebook editor did not open' - ); - }); - - after(async function () { - // Defensive cleanup: never leave the driver stuck inside a webview frame, and close tabs. - await new WebView().switchBack().catch(() => undefined); - await new EditorView().closeAllEditors().catch(() => undefined); - }); - - it('creates an environment, connects the kernel, runs the cell and renders output', async function () { - await createEnvironment(environmentName); - await selectEnvironmentForNotebook(environmentName); - - const renderedOutput = await runAndAwaitOutput(EXPECTED_OUTPUT, OUTPUT_TIMEOUT); - expect(renderedOutput).to.contain(EXPECTED_OUTPUT); - }); - - /** - * Drives `deepnote.environments.create`: pick interpreter -> name -> skip packages -> - * skip description. Retries when the Python extension has not finished discovering an - * interpreter yet (the command shows an error and returns instead of opening a quick pick). - */ - async function createEnvironment(name: string): Promise { - let lastError: unknown; - - for (let attempt = 1; attempt <= MAX_CREATE_ATTEMPTS; attempt++) { - await new Workbench().executeCommand(CREATE_ENV_COMMAND); - - // Either the interpreter quick pick opens, or (no interpreter discovered yet) the - // command shows a "No Python interpreters found" notification and returns. - const interpreterPick = await tryOpenInputBox(5_000); - if (!interpreterPick) { - await dismissAllNotifications(); - await driver.sleep(INTERPRETER_RETRY_DELAY); - lastError = new Error('interpreter quick pick did not appear (interpreter discovery not ready?)'); - continue; - } - - try { - await driver.wait( - async () => (await interpreterPick.getQuickPicks()).length > 0, - QUICK_PICK_TIMEOUT, - 'no Python interpreters were listed' - ); - } catch (error) { - await interpreterPick.cancel().catch(() => undefined); - await dismissAllNotifications(); - await driver.sleep(INTERPRETER_RETRY_DELAY); - lastError = error; - continue; - } - - await interpreterPick.selectQuickPick(0); - - const nameBox = await InputBox.create(); - await nameBox.setText(name); - await nameBox.confirm(); - - // Packages (optional) — leave empty. - await (await InputBox.create()).confirm(); - - // Description (optional) — leave empty. - await (await InputBox.create()).confirm(); - - // Treat both the success toast and the "already exists" guard as success: a leftover - // environment from a previous/retried run is fine — it will be selected next. - await waitForNotification(/created successfully|already exists/i, ENV_CREATED_TIMEOUT, false); - return; - } - - throw new Error( - `Failed to create a Deepnote environment after ${MAX_CREATE_ATTEMPTS} attempts. ` + - `Ensure the Python extension is installed and an interpreter is discoverable. ` + - `Last error: ${String(lastError)}` - ); - } - - /** - * Drives `deepnote.environments.selectForNotebook`. Selecting the environment rebuilds and - * explicitly selects the notebook's kernel controller (provisioning the venv + toolkit), - * which is what "wait for the kernel to connect" means in this extension. - */ - async function selectEnvironmentForNotebook(name: string): Promise { - // The command requires an active `deepnote` notebook — make sure it's focused. - await new EditorView().openEditor(NOTEBOOK_FILE_NAME); - - // Clear the "select an environment" prompt and any other toasts; they can overlap the - // quick pick and intercept clicks. - await dismissAllNotifications(); - - await new Workbench().executeCommand(SELECT_ENV_COMMAND); - - const environmentPick = await InputBox.create(QUICK_PICK_TIMEOUT); - // Filter to the environment by name and accept with Enter rather than clicking the row: - // the quick-pick row contains a description `

` that can intercept a positional click. - await environmentPick.setText(name); - await driver.wait( - async () => (await environmentPick.getQuickPicks()).length > 0, - QUICK_PICK_TIMEOUT, - 'environment quick pick was empty' - ); - await environmentPick.confirm(); - - // Best-effort wait for the "switched successfully" toast; the authoritative gate is the - // rendered output below, so a missed (auto-dismissed) toast must not fail the test. - await waitForNotification(/switched successfully/i, KERNEL_CONNECT_TIMEOUT, false); - } - - /** - * Clicks the notebook editor toolbar's "Run All" button. The command-palette entry for - * `deepnote.runallcells` ("Jupyter: Run All Cells") is gated behind context keys - * (`deepnote.ispythonornativeactive`, …) that are not reliably set under automation, so driving - * it through `Workbench.executeCommand` can silently miss and trigger the wrong command. - */ - async function clickRunAll(): Promise { - await new EditorView().openEditor(NOTEBOOK_FILE_NAME); - - const runAllButton = await driver.wait( - async () => { - const [button] = await driver.findElements(By.css('a.action-label[aria-label="Run All"]')); - - return button; - }, - WORKBENCH_TIMEOUT, - 'notebook "Run All" button did not appear' - ); - await runAllButton.click(); - } - - /** - * Opens a file that lives in the currently-open workspace folder via Quick Open ("Go to - * File..."), matching by file name. Unlike the simple Open File dialog (where Enter does not - * accept a typed path), Quick Open reliably opens the highlighted match on confirm. - */ - async function openWorkspaceFile(fileName: string): Promise { - await new Workbench().executeCommand('Go to File...'); - - const quickOpen = await InputBox.create(QUICK_PICK_TIMEOUT); - await quickOpen.setText(fileName); - await driver.wait( - async () => (await quickOpen.getQuickPicks()).length > 0, - QUICK_PICK_TIMEOUT, - `"${fileName}" did not appear in Quick Open` - ); - await quickOpen.confirm(); - } - - /** - * Opens an absolute folder path as the workspace root via "File: Open Folder...". Opening a - * folder reloads the VS Code window. In the simple folder dialog, Enter navigates *into* a - * directory rather than accepting it as the workspace — the deterministic accept is the dialog's - * "OK" button — so we type the path, click OK, and wait for the pre-reload workbench element to - * detach (reload started). We retry the whole interaction defensively. The caller then waits for - * the new workbench to mount. - */ - async function openFolderViaDialog(folder: string): Promise { - for (let attempt = 1; attempt <= FOLDER_OPEN_ATTEMPTS; attempt++) { - const previousWorkbench = await driver.findElement(By.css('.monaco-workbench')); - - await new Workbench().executeCommand('File: Open Folder...'); - const dialog = await InputBox.create(QUICK_PICK_TIMEOUT); - await dialog.setText(folder); - - // The simple dialog resolves the typed path asynchronously (listing the enclosing - // directory); wait for that listing and add a short settle before accepting. - await driver - .wait( - async () => (await dialog.getQuickPicks()).length > 0, - QUICK_PICK_TIMEOUT, - 'dialog did not resolve path' - ) - .catch(() => undefined); - await driver.sleep(DIALOG_RESOLVE_DELAY); - - const accepted = await clickDialogOkButton(); - if (!accepted) { - await new InputBox().cancel().catch(() => undefined); - continue; - } - - const reloaded = await driver - .wait(async () => { - try { - await previousWorkbench.getTagName(); - - return false; - } catch { - return true; - } - }, FOLDER_RELOAD_TIMEOUT) - .then(() => true) - .catch(() => false); - if (reloaded) { - return; - } - - // The folder did not open this time; dismiss any lingering dialog and retry. - await new InputBox().cancel().catch(() => undefined); - } - - throw new Error(`Failed to open folder "${folder}" after ${FOLDER_OPEN_ATTEMPTS} attempts`); - } - - /** Clicks the simple file/folder dialog's "OK" button. Returns false if it could not be found. */ - async function clickDialogOkButton(): Promise { - const buttons = await driver - .findElements(By.css('.quick-input-widget .monaco-button.monaco-text-button')) - .catch(() => []); - for (const button of buttons) { - const text = (await button.getText().catch(() => '')).trim(); - if (text === 'OK') { - await button.click(); - - return true; - } - } - - return false; - } - - /** - * Clicks "Run All" and polls the notebook output webview until the expected text renders, - * re-issuing "Run All" periodically. The first run can be dropped when the kernel has only just - * finished connecting, so we keep nudging it until output appears (re-running `print(...)` is - * harmless). - */ - async function runAndAwaitOutput(expected: string, timeout: number): Promise { - const deadline = Date.now() + timeout; - let lastRunAt = 0; - let lastText = ''; - - while (Date.now() < deadline) { - if (Date.now() - lastRunAt > RUN_ALL_REISSUE_INTERVAL) { - await clickRunAll().catch(() => undefined); - lastRunAt = Date.now(); - } - - lastText = await readRenderedOutput(); - if (lastText.includes(expected)) { - return lastText; - } - - await driver.sleep(2_000); - } - - throw new Error( - `Timed out after ${timeout}ms waiting for rendered output to contain "${expected}". ` + - `Last observed output: ${JSON.stringify(lastText)}` - ); - } - - /** - * Reads the notebook cell output once. - * - * Output lives two iframes deep (iframe.webview.ready -> #active-frame). We only attempt to - * switch when an output webview iframe actually exists (`getViewToSwitchTo`), and we read - * output-specific elements inside the frame — so we never match the cell's source code that - * is visible in the editor of the main document. Returns '' when no output is present yet. - */ - async function readRenderedOutput(): Promise { - const webView = new WebView(); - const outputFrame = await webView.getViewToSwitchTo().catch(() => undefined); - if (!outputFrame) { - return ''; - } - - let text = ''; - try { - await webView.switchToFrame(5_000); - const elements = await webView.findWebElements(By.css(OUTPUT_SELECTOR)); - const texts = await Promise.all(elements.map((element) => element.getText().catch(() => ''))); - text = texts.join('\n').trim(); - - // Fallback: if the renderer used unexpected classes, read the frame body — safe here - // because we have confirmed we are inside the output iframe, not the editor. - if (!text) { - const body = await webView.findWebElement(By.css('body')).catch(() => undefined); - text = body ? (await body.getText().catch(() => '')).trim() : ''; - } - } catch { - // Frame went stale or output not painted yet — treat as no output this tick. - } finally { - await webView.switchBack().catch(() => undefined); - } - - return text; - } - - async function tryOpenInputBox(timeout: number): Promise { - try { - return await InputBox.create(timeout); - } catch { - return undefined; - } - } - - async function dismissAllNotifications(): Promise { - const notifications = await new Workbench().getNotifications().catch(() => [] as Notification[]); - for (const notification of notifications) { - await notification.dismiss().catch(() => undefined); - } - } - - async function waitForNotification( - pattern: RegExp, - timeout: number, - required: boolean - ): Promise { - try { - return (await driver.wait( - async () => { - const notifications = await new Workbench().getNotifications().catch(() => [] as Notification[]); - for (const notification of notifications) { - const message = await notification.getMessage().catch(() => ''); - if (pattern.test(message)) { - return notification; - } - } - return undefined; - }, - timeout, - `timed out waiting for a notification matching ${pattern}` - )) as Notification; - } catch (error) { - if (required) { - throw error; - } - return undefined; - } - } -}); -``` - ---- - -## 7. How the hard parts work - -### 7.1 Reading rendered output from nested iframes - -ExTester has **no notebook page object**. VS Code renders cell output inside **two nested -iframes**: - -``` -main VS Code document -└─ iframe.webview.ready ← outer (ExTester locator: iframe[class='webview ready']) - └─ iframe#active-frame ← inner (ExTester locator: #active-frame) - └─ .output_container .output ← the rendered output lives here -``` - -ExTester's `WebView.switchToFrame()` descends **exactly those two levels** (verified in its -`WebviewMixin` source), which is why it lines up with the notebook output area. The test's -`waitForRenderedOutput` helper: - -1. Constructs `new WebView()` and calls `getViewToSwitchTo()` — this returns the outer - webview iframe **only if one exists**. The output iframe is created lazily once a cell - produces output, so before execution it returns `undefined` and we simply keep polling. - **This gate is what prevents a false positive**: without it, `switchToFrame()` would - no-op (stay in the main document) and reading the body would match the cell's *source* - `print("hello world")` shown in the editor. -2. Once a frame exists, `switchToFrame(5_000)` descends both levels. -3. Reads **output-scoped** elements (`.output_container, .output, .rendered-output`) — not - the whole body — and joins their text. (A body-text fallback is used only when those - classes are absent, and it's safe there because we've confirmed we are inside the output - iframe.) -4. **Always `switchBack()` in `finally`** — touching the main document while switched into - a frame throws `StaleElementReferenceError`. -5. Loops until the text contains `hello world` or the timeout elapses. - -**Fallback if `WebView` ever mis-targets the frame** (e.g. VS Code adds a class so the -exact `iframe[class='webview ready']` locator misses): drive Selenium directly — -`driver.switchTo().frame(driver.findElement(By.css('iframe.webview')))` then -`driver.switchTo().frame(driver.findElement(By.id('active-frame')))`, read, then -`driver.switchTo().defaultContent()`. - -**Note on shadow DOM:** rich/widget renderers may render into a shadow root (unreachable by -plain CSS). Plain stdout text (our case) renders in light DOM and is reachable. - -### 7.2 Driving QuickPicks & InputBoxes - -`Workbench.executeCommand(label)` opens the palette and matches the **friendly command -title** (category + title), so we pass e.g. `'Deepnote: Create Environment'`, not the -command id. Since VS Code 1.44, `InputBox` represents both text prompts and QuickPicks; we -re-create it between steps because each step replaces the DOM. - -Flakiness guards built into the test: - -- **Interpreter-discovery latency.** The Python extension populates - `api.environments.known` asynchronously; if empty, the create command shows *"No Python - interpreters found"* and returns (no quick pick). The test detects the missing quick pick - (`tryOpenInputBox` times out), dismisses notifications, waits, and **retries up to 6 - times**. -- **Idempotent environment creation** with a stable name (`E2E Hello Env`): `createEnvironment` - treats the "name already exists" guard as success, so a leftover env from a Mocha retry (or a - persistent local instance) is reused rather than colliding — and its venv is reused too. -- **Notification waits are best-effort** for the transient success toasts; the authoritative - gate is the rendered output, so an auto-dismissed toast never fails the test. -- **`driver.wait(...)`** is used for every asynchronous UI state instead of bare sleeps - where possible. - ---- - -## 8. Running it - -`extest setup-and-run` loads the extension from the **built** `dist/` output and launches a -real VS Code window. First-time sequence: - -```bash -npm run compile # build the extension under test (produces dist/extension.node.js) -npm run setup:e2e # download test VS Code + ChromeDriver and install ms-python.python (one-time) -npm run compile-e2e # build the test sources (test/e2e → out/e2e); or run compile-e2e-watch -npm run test:e2e # extest packages the extension, downloads/launches VS Code, runs the tests -``` - -- **Linux is headless** → install the Electron/Chromium system libraries and Xvfb (§0), then - wrap the run: - ```bash - xvfb-run --auto-servernum --server-args='-screen 0 1920x1080x24' npm run test:e2e - ``` - macOS/Windows run directly. (ExTester always launches VS Code with `--no-sandbox`, so the - Ubuntu 24.04 AppArmor user-namespace restriction does not block the test — no sysctl needed; - inside a container where that sysctl is read-only, `--no-sandbox` is what makes it work.) -- A **venv-capable Python interpreter** must be discoverable (§0), and creating the environment - installs the Deepnote toolkit (**network required**); the first kernel start can take minutes. -- ExTester caches VS Code/ChromeDriver under `test-resources/` (by default - `$TMPDIR/test-resources`, e.g. `/tmp/test-resources`) after the first download. -- Failure screenshots are written under `test-resources/**/_screenshots/` (and any - `_screenshots` under `out/e2e/`). - -**Compatibility note:** ExTester `8.23.0` supports a floating window of recent VS Code -minors; `-c max` picks the newest. Our extension's `engines.vscode` is `^1.95.0`, so any -1.x ≥ 1.95 (including `max`) is compatible. Node should be an active LTS (the repo's -`.nvmrc`; local dev uses Node 22). - ---- - -## 9. CI integration - -Add a dedicated job (separate from lint/typecheck/unit so its weight and flakiness are -isolated). Linux must run under a virtual framebuffer. - -```yaml -e2e: - name: E2E (ExTester) - runs-on: ubuntu-latest - timeout-minutes: 45 - steps: - - uses: actions/checkout@v6 - - uses: actions/setup-node@v6 - with: { cache: npm, node-version-file: .nvmrc } - - uses: actions/setup-python@v5 # interpreter for the Deepnote environment - with: { python-version: '3.12' } - - run: npm ci --prefer-offline --no-audit - - run: npm run compile # build the extension under test - # Electron/Chromium runtime libraries (the test VS Code won't launch without them) + Xvfb - - run: | - sudo apt-get update - sudo apt-get install -y xvfb \ - libatk1.0-0t64 libatk-bridge2.0-0t64 libcups2t64 libgtk-3-0t64 libgdk-pixbuf-2.0-0 \ - libgbm1 libasound2t64 libnss3 libnspr4 libxss1 libxshmfence1 libdrm2 libxkbcommon0 \ - libxcomposite1 libxdamage1 libxrandr2 libxfixes3 libxext6 libxrender1 libpango-1.0-0 \ - libcairo2 libatspi2.0-0 libx11-xcb1 libxcb-dri3-0 libxtst6 libsecret-1-0 \ - libgssapi-krb5-2 libdbus-1-3 libexpat1 python3.12-venv python3-pip - # Download the test VS Code and install ms-python.python (§0). ExTester always launches with - # --no-sandbox, so no AppArmor sysctl is required; no proposed-API patch is needed either. - - run: npm run setup:e2e - - name: Run E2E - uses: nick-fields/retry@v4 # absorb transient UI flakiness - with: - timeout_minutes: 40 - max_attempts: 2 - command: xvfb-run --auto-servernum --server-args='-screen 0 1920x1080x24' npm run test:e2e - - uses: actions/upload-artifact@v7 - if: failure() - with: - name: e2e-screenshots - path: | - test-resources/**/_screenshots/**/*.png - out/e2e/**/_screenshots/**/*.png -``` - -Notes: -- ExTester caches VS Code/ChromeDriver under `test-resources/` itself; optionally cache that - directory to speed reruns. -- On macOS/Windows runners drop the `xvfb-run` wrapper. -- **Network**: creating the environment installs the Deepnote toolkit (pip). The runner - needs outbound network, or a pre-seeded/offline toolkit — the single biggest portability - risk; flag it when enabling the job. - ---- - -## 10. Gotchas & flakiness mitigation - -- **Bump timeouts.** Mocha's 2 s default is unusable; the suite uses `timeout: 1500000` (25 min) - and the test sets a 22-minute per-test timeout (overrides the suite default). -- **Prefer `executeCommand` over clicking** *for palette commands that are always enabled* — but - some are not. `deepnote.runallcells` is gated behind context keys that don't hold under - automation, so the test clicks the toolbar's "Run All" button instead (§0). -- **The simple file dialog accepts via its "OK" button, not Enter.** Enter navigates into a - directory. Type the path, then click `.quick-input-widget .monaco-button.monaco-text-button` - whose text is "OK" (§0, `clickDialogOkButton`). -- **Re-issue "Run All"** while polling for output — the first run can be dropped right after the - kernel connects (§0, `runAndAwaitOutput`). -- **Always `switchBack()` in `finally`** around any webview interaction. -- **Use the safe constructors** (`await InputBox.create()`, `await EditorView().openEditor(...)`) - rather than `new InputBox()` when the element may not be ready. -- **Clean up** in `after`: `switchBack()` defensively and `closeAllEditors()`. -- **macOS caveat** (if you extend the suite): native title-bar menus, native context menus, - and native file dialogs are unsupported by ExTester — use command-palette equivalents and - VS Code "simple" dialogs (we already force `files.simpleDialog.enable`). -- **`getCurrentChannel`/`getLaunchConfiguration`** are broken on Windows/Linux for VS Code - ≥ 1.87 — avoid them. -- **chai v5** is ESM-only — stay on chai v4 (the repo's version). - ---- - -## 11. Where ExTester fits vs the other test layers - -This repo already has three layers; ExTester is a fourth that fills the "real rendered UX" -gap. It **replaces nothing**. - -| Layer | Runner | Covers | -| --- | --- | --- | -| Unit (`*.unit.test.ts`) | Mocha + Chai (`build/.mocha.unittests.js.json`) | Pure logic, no VS Code host. | -| Integration (`*.vscode.test.ts`) | `@vscode/test-electron` (`src/test/standardTest.node.ts`) | Runs **inside** the extension host with the `vscode` API. | -| Smoke (`src/smoke`) | custom harness | Broad checks. | -| **E2E (this) (`*.e2e.test.ts`)** | **ExTester** (`test/e2e`) | **Black-box, real VS Code UI** — open → env → kernel → run → rendered output. | - -**Rule of thumb:** assert *rendered pixels* with ExTester; assert *output data/semantics* -with the integration layer. For output-heavy correctness, the in-host API route (what -upstream microsoft/vscode-jupyter does — drive `notebook.cell.execute`, read -`cell.outputs[].items[].data` via `TextDecoder`, no iframes) is more reliable; add such -tests as the volume layer over time. Reserve ExTester for a *small* number of high-value -end-to-end smoke tests like the one shipped here. - ---- - -## 12. Risks & mitigations - -| Risk | Mitigation | -| --- | --- | -| No notebook page object in ExTester | Use `WebView` (its 2-level frame switch matches the notebook output iframes); assert on output-scoped selectors; raw-Selenium fallback documented (§7.1). | -| Reading editor source as if it were output | Gate frame switching on `getViewToSwitchTo()` and read output-scoped selectors only (§7.1). | -| Python interpreter not discovered in time | Install `ms-python.python`; retry the create command up to 6×; `setup-python` in CI. | -| Env creation needs network (pip toolkit) | Documented prerequisite; consider an offline/seeded toolkit for CI. | -| First kernel start is slow | Long polls (5 min on output / kernel connect); 13-min per-test timeout; `nick-fields/retry`. | -| Linux GUI / sandbox | `xvfb-run`; AppArmor sysctl on Ubuntu 24.04. | -| UI flakiness | `driver.wait` everywhere; `switchBack` in `finally`; screenshots on failure; one Mocha retry. | -| chai v5 ESM breakage | Pin chai v4 (already the repo's version). | -| Workspace-trust modal blocks automation | `security.workspace.trust.enabled: false` in test settings. | - ---- - -## Appendix A — ExTester API surface used - -All imported from `vscode-extension-tester` (re-exported from `@redhat-developer/page-objects`, -`@redhat-developer/locators`, and `selenium-webdriver`). Signatures verified against the -installed `8.23.0`. - -| Symbol | Member | Notes | -| --- | --- | --- | -| `VSBrowser` | `instance` | singleton | -| | `instance.driver` | the selenium `WebDriver` (`.wait`, `.sleep`, …) | -| | `openResources(...paths, cb?)` | open files/folders (absolute paths) | -| | `waitForWorkbench(timeout?)` | wait until workbench is ready | -| | `takeScreenshot(name)` | manual screenshot → `/_screenshots` | -| `Workbench` | `executeCommand(label)` | matches the friendly command **title** | -| | `getNotifications()` | current toast notifications | -| `EditorView` | `openEditor(title)` | focus/return an editor tab | -| | `getOpenEditorTitles()` | open tab titles | -| | `closeAllEditors()` | cleanup | -| `InputBox` | `static create(timeout?)` | safe constructor; represents prompts **and** QuickPicks | -| | `setText` / `confirm` / `cancel` | text prompts | -| | `getQuickPicks()` / `selectQuickPick(idxOrText)` / `findQuickPick` | quick picks (substring match) | -| | `hasProgress()` | true while the input shows a progress bar | -| `WebView` | `getViewToSwitchTo()` | returns the outer webview iframe element or `undefined` | -| | `switchToFrame(timeout?)` / `switchBack()` | descend into / out of the (2-level) webview iframes | -| | `findWebElement(locator)` / `findWebElements(locator)` | query inside the frame | -| `Notification` | `getMessage()` / `getType()` / `dismiss()` / `takeAction(title)` | toast inspection | -| `By`, `until`, `Key`, `WebDriver` | — | re-exported selenium primitives | - ---- - -## Appendix B — Deepnote command-id reference - -The commands this test drives, with their command ids and palette labels (from -`package.json` + `package.nls.json`): - -| Palette label (used in the test) | Command id | Category | -| --- | --- | --- | -| `Deepnote: Create Environment` | `deepnote.environments.create` | Deepnote | -| `Deepnote: Select Environment for Notebook` | `deepnote.environments.selectForNotebook` | Deepnote | -| `Jupyter: Run All Cells` | `deepnote.runallcells` | Jupyter | - -Notebook type registered for `.deepnote`: **`deepnote`** -(`contributes.notebooks[].type`, selector `*.deepnote`). - ---- - -## References - -ExTester: -- Repo / wiki: https://github.com/redhat-developer/vscode-extension-tester · - https://github.com/redhat-developer/vscode-extension-tester/wiki -- Test setup (CLI): https://github.com/redhat-developer/vscode-extension-tester/wiki/Test-Setup -- WebView page object: https://github.com/redhat-developer/vscode-extension-tester/wiki/WebView -- Workbench / Input: https://github.com/redhat-developer/vscode-extension-tester/wiki/Workbench · - https://github.com/redhat-developer/vscode-extension-tester/wiki/Input -- Example project: https://github.com/redhat-developer/vscode-extension-tester-example -- Known issues (AppArmor, etc.): https://github.com/redhat-developer/vscode-extension-tester/blob/main/KNOWN_ISSUES.md - -Real-world usage studied: redhat-developer/{vscode-quarkus, vscode-server-connector, -vscode-rsp-ui} and the framework's own `tests/test-project`. - -VS Code notebook internals (output DOM / iframes): VS Code source `webviewPreloads.ts`, -`pre/index.html`; Notebook API guide -https://code.visualstudio.com/api/extension-guides/notebook ; built-in commands -https://code.visualstudio.com/api/references/commands - -Upstream test approach (API-driven alternative): microsoft/vscode-jupyter -`src/test/datascience/notebook/helper.ts`. - -Codebase anchors: `src/notebooks/deepnote/deepnoteKernelAutoSelector.node.ts`, -`src/kernels/deepnote/environments/deepnoteEnvironmentsView.node.ts`, -`src/notebooks/deepnote/deepnoteSerializer.ts`. From fdba91f029648dbf8f75737af5d577de8e1d4298 Mon Sep 17 00:00:00 2001 From: tomas Date: Thu, 25 Jun 2026 13:07:17 +0000 Subject: [PATCH 6/7] refactor(e2e): extract interaction helpers into test/e2e/helpers modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the reusable ExTester interaction helpers out of the single large test file into focused, reusable modules under test/e2e/helpers/: - constants timeouts + the output-iframe selector - fixtures copyFixtureToTempDir - notifications dismissAllNotifications, waitForNotification - quickInput tryOpenInputBox, clickDialogOkButton - workspace openFolderViaDialog, openWorkspaceFile - notebook clickRunAll, readRenderedOutput, runAndAwaitOutput - deepnoteEnvironment createEnvironment, selectEnvironmentForNotebook - index barrel re-export Helpers take the WebDriver from VSBrowser.instance.driver and receive the notebook name as a parameter instead of closing over suite state, so future suites can reuse them. helloWorld.e2e.test.ts shrinks from 439 to 87 lines and is now just the suite wiring. Behaviour is unchanged — verified passing. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01LQk7n13UfmeDo2ujy8X4LX --- test/e2e/helpers/constants.ts | 24 ++ test/e2e/helpers/deepnoteEnvironment.ts | 111 +++++++ test/e2e/helpers/fixtures.ts | 24 ++ test/e2e/helpers/index.ts | 8 + test/e2e/helpers/notebook.ts | 94 ++++++ test/e2e/helpers/notifications.ts | 46 +++ test/e2e/helpers/quickInput.ts | 37 +++ test/e2e/helpers/workspace.ts | 84 +++++ test/e2e/suite/helloWorld.e2e.test.ts | 400 ++---------------------- 9 files changed, 452 insertions(+), 376 deletions(-) create mode 100644 test/e2e/helpers/constants.ts create mode 100644 test/e2e/helpers/deepnoteEnvironment.ts create mode 100644 test/e2e/helpers/fixtures.ts create mode 100644 test/e2e/helpers/index.ts create mode 100644 test/e2e/helpers/notebook.ts create mode 100644 test/e2e/helpers/notifications.ts create mode 100644 test/e2e/helpers/quickInput.ts create mode 100644 test/e2e/helpers/workspace.ts diff --git a/test/e2e/helpers/constants.ts b/test/e2e/helpers/constants.ts new file mode 100644 index 0000000000..900b544367 --- /dev/null +++ b/test/e2e/helpers/constants.ts @@ -0,0 +1,24 @@ +// Shared timeouts (ms) and selectors for the ExTester E2E suite. +// UI ops are slow and the first kernel start is the slowest step. + +export const WORKBENCH_TIMEOUT = 60_000; +export const QUICK_PICK_TIMEOUT = 30_000; +export const ENV_CREATED_TIMEOUT = 120_000; +export const KERNEL_CONNECT_TIMEOUT = 300_000; +export const OUTPUT_TIMEOUT = 300_000; + +// How often to re-issue "Run All" while waiting for output — the first run can be dropped right +// after the kernel connects. +export const RUN_ALL_REISSUE_INTERVAL = 25_000; + +export const INTERPRETER_RETRY_DELAY = 5_000; +export const MAX_CREATE_ATTEMPTS = 6; + +// The in-window simple file/folder dialog needs a beat to resolve a typed path before it accepts. +export const DIALOG_RESOLVE_DELAY = 1_500; +export const FOLDER_OPEN_ATTEMPTS = 5; +export const FOLDER_RELOAD_TIMEOUT = 12_000; + +// Selectors that only exist inside the notebook output iframe (`#active-frame`), +// so reading them cannot accidentally match the cell's source in the editor. +export const OUTPUT_SELECTOR = '.output_container, .output, .rendered-output'; diff --git a/test/e2e/helpers/deepnoteEnvironment.ts b/test/e2e/helpers/deepnoteEnvironment.ts new file mode 100644 index 0000000000..3eaa7a044d --- /dev/null +++ b/test/e2e/helpers/deepnoteEnvironment.ts @@ -0,0 +1,111 @@ +import { EditorView, InputBox, VSBrowser, Workbench } from 'vscode-extension-tester'; + +import { + ENV_CREATED_TIMEOUT, + INTERPRETER_RETRY_DELAY, + KERNEL_CONNECT_TIMEOUT, + MAX_CREATE_ATTEMPTS, + QUICK_PICK_TIMEOUT +} from './constants'; +import { dismissAllNotifications, waitForNotification } from './notifications'; +import { tryOpenInputBox } from './quickInput'; + +// Command palette labels (category + title) the way `Workbench.executeCommand` matches them. +const CREATE_ENV_COMMAND = 'Deepnote: Create Environment'; +const SELECT_ENV_COMMAND = 'Deepnote: Select Environment for Notebook'; + +/** + * Drives `deepnote.environments.create`: pick interpreter -> name -> skip packages -> skip + * description. Retries when the Python extension has not finished discovering an interpreter yet + * (the command shows an error and returns instead of opening a quick pick). Idempotent — the + * "already exists" guard is treated as success so a leftover environment from a previous/retried run + * is reused rather than colliding. + */ +export async function createEnvironment(name: string): Promise { + const driver = VSBrowser.instance.driver; + let lastError: unknown; + + for (let attempt = 1; attempt <= MAX_CREATE_ATTEMPTS; attempt++) { + await new Workbench().executeCommand(CREATE_ENV_COMMAND); + + // Either the interpreter quick pick opens, or (no interpreter discovered yet) the command + // shows a "No Python interpreters found" notification and returns. + const interpreterPick = await tryOpenInputBox(5_000); + if (!interpreterPick) { + await dismissAllNotifications(); + await driver.sleep(INTERPRETER_RETRY_DELAY); + lastError = new Error('interpreter quick pick did not appear (interpreter discovery not ready?)'); + continue; + } + + try { + await driver.wait( + async () => (await interpreterPick.getQuickPicks()).length > 0, + QUICK_PICK_TIMEOUT, + 'no Python interpreters were listed' + ); + } catch (error) { + await interpreterPick.cancel().catch(() => undefined); + await dismissAllNotifications(); + await driver.sleep(INTERPRETER_RETRY_DELAY); + lastError = error; + continue; + } + + await interpreterPick.selectQuickPick(0); + + const nameBox = await InputBox.create(); + await nameBox.setText(name); + await nameBox.confirm(); + + // Packages (optional) — leave empty. + await (await InputBox.create()).confirm(); + + // Description (optional) — leave empty. + await (await InputBox.create()).confirm(); + + // Treat both the success toast and the "already exists" guard as success: a leftover + // environment from a previous/retried run is fine — it will be selected next. + await waitForNotification(/created successfully|already exists/i, ENV_CREATED_TIMEOUT, false); + return; + } + + throw new Error( + `Failed to create a Deepnote environment after ${MAX_CREATE_ATTEMPTS} attempts. ` + + `Ensure the Python extension is installed and an interpreter is discoverable. ` + + `Last error: ${String(lastError)}` + ); +} + +/** + * Drives `deepnote.environments.selectForNotebook`. Selecting the environment rebuilds and + * explicitly selects the notebook's kernel controller (provisioning the venv + toolkit), which is + * what "wait for the kernel to connect" means in this extension. + */ +export async function selectEnvironmentForNotebook(name: string, notebookFileName: string): Promise { + const driver = VSBrowser.instance.driver; + + // The command requires an active `deepnote` notebook — make sure it's focused. + await new EditorView().openEditor(notebookFileName); + + // Clear the "select an environment" prompt and any other toasts; they can overlap the quick pick + // and intercept clicks. + await dismissAllNotifications(); + + await new Workbench().executeCommand(SELECT_ENV_COMMAND); + + const environmentPick = await InputBox.create(QUICK_PICK_TIMEOUT); + // Filter to the environment by name and accept with Enter rather than clicking the row: the + // quick-pick row contains a description `

` that can intercept a positional click. + await environmentPick.setText(name); + await driver.wait( + async () => (await environmentPick.getQuickPicks()).length > 0, + QUICK_PICK_TIMEOUT, + 'environment quick pick was empty' + ); + await environmentPick.confirm(); + + // Best-effort wait for the "switched successfully" toast; the authoritative gate is the rendered + // output, so a missed (auto-dismissed) toast must not fail the test. + await waitForNotification(/switched successfully/i, KERNEL_CONNECT_TIMEOUT, false); +} diff --git a/test/e2e/helpers/fixtures.ts b/test/e2e/helpers/fixtures.ts new file mode 100644 index 0000000000..eef14b063a --- /dev/null +++ b/test/e2e/helpers/fixtures.ts @@ -0,0 +1,24 @@ +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; + +export interface FixtureCopy { + /** The throwaway temp directory the fixture was copied into (suitable as a workspace folder). */ + tempDir: string; + /** The absolute path to the copied fixture file inside `tempDir`. */ + filePath: string; +} + +/** + * Copies a fixture from `test/e2e/fixtures` into a fresh throwaway temp directory and returns both + * paths. Execution dirties the notebook, so working on a throwaway copy keeps the committed fixture + * pristine and avoids save prompts. + */ +export function copyFixtureToTempDir(fixtureName: string): FixtureCopy { + const source = path.resolve(process.cwd(), 'test', 'e2e', 'fixtures', fixtureName); + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'deepnote-e2e-')); + const filePath = path.join(tempDir, fixtureName); + fs.copyFileSync(source, filePath); + + return { tempDir, filePath }; +} diff --git a/test/e2e/helpers/index.ts b/test/e2e/helpers/index.ts new file mode 100644 index 0000000000..c33f734de7 --- /dev/null +++ b/test/e2e/helpers/index.ts @@ -0,0 +1,8 @@ +// Barrel re-export so suites can `import { … } from '../helpers'`. +export * from './constants'; +export * from './deepnoteEnvironment'; +export * from './fixtures'; +export * from './notebook'; +export * from './notifications'; +export * from './quickInput'; +export * from './workspace'; diff --git a/test/e2e/helpers/notebook.ts b/test/e2e/helpers/notebook.ts new file mode 100644 index 0000000000..7420a60a6d --- /dev/null +++ b/test/e2e/helpers/notebook.ts @@ -0,0 +1,94 @@ +import { By, EditorView, VSBrowser, WebView } from 'vscode-extension-tester'; + +import { OUTPUT_SELECTOR, RUN_ALL_REISSUE_INTERVAL, WORKBENCH_TIMEOUT } from './constants'; + +/** + * Focuses the given notebook editor and clicks its toolbar "Run All" button. The command-palette + * entry for `deepnote.runallcells` ("Jupyter: Run All Cells") is gated behind context keys + * (`deepnote.ispythonornativeactive`, …) that are not reliably set under automation, so driving it + * through `Workbench.executeCommand` can silently miss and trigger the wrong command. + */ +export async function clickRunAll(notebookFileName: string): Promise { + const driver = VSBrowser.instance.driver; + + await new EditorView().openEditor(notebookFileName); + + const runAllButton = await driver.wait( + async () => { + const [button] = await driver.findElements(By.css('a.action-label[aria-label="Run All"]')); + + return button; + }, + WORKBENCH_TIMEOUT, + 'notebook "Run All" button did not appear' + ); + await runAllButton.click(); +} + +/** + * Reads the notebook cell output once. + * + * Output lives two iframes deep (iframe.webview.ready -> #active-frame). We only attempt to switch + * when an output webview iframe actually exists (`getViewToSwitchTo`), and we read output-specific + * elements inside the frame — so we never match the cell's source code that is visible in the editor + * of the main document. Returns '' when no output is present yet. + */ +export async function readRenderedOutput(): Promise { + const webView = new WebView(); + const outputFrame = await webView.getViewToSwitchTo().catch(() => undefined); + if (!outputFrame) { + return ''; + } + + let text = ''; + try { + await webView.switchToFrame(5_000); + const elements = await webView.findWebElements(By.css(OUTPUT_SELECTOR)); + const texts = await Promise.all(elements.map((element) => element.getText().catch(() => ''))); + text = texts.join('\n').trim(); + + // Fallback: if the renderer used unexpected classes, read the frame body — safe here because + // we have confirmed we are inside the output iframe, not the editor. + if (!text) { + const body = await webView.findWebElement(By.css('body')).catch(() => undefined); + text = body ? (await body.getText().catch(() => '')).trim() : ''; + } + } catch { + // Frame went stale or output not painted yet — treat as no output this tick. + } finally { + await webView.switchBack().catch(() => undefined); + } + + return text; +} + +/** + * Clicks "Run All" and polls the notebook output webview until the expected text renders, re-issuing + * "Run All" periodically. The first run can be dropped when the kernel has only just finished + * connecting, so we keep nudging it until output appears (re-running `print(...)` is harmless). + */ +export async function runAndAwaitOutput(notebookFileName: string, expected: string, timeout: number): Promise { + const driver = VSBrowser.instance.driver; + const deadline = Date.now() + timeout; + let lastRunAt = 0; + let lastText = ''; + + while (Date.now() < deadline) { + if (Date.now() - lastRunAt > RUN_ALL_REISSUE_INTERVAL) { + await clickRunAll(notebookFileName).catch(() => undefined); + lastRunAt = Date.now(); + } + + lastText = await readRenderedOutput(); + if (lastText.includes(expected)) { + return lastText; + } + + await driver.sleep(2_000); + } + + throw new Error( + `Timed out after ${timeout}ms waiting for rendered output to contain "${expected}". ` + + `Last observed output: ${JSON.stringify(lastText)}` + ); +} diff --git a/test/e2e/helpers/notifications.ts b/test/e2e/helpers/notifications.ts new file mode 100644 index 0000000000..4d9a0f1c1f --- /dev/null +++ b/test/e2e/helpers/notifications.ts @@ -0,0 +1,46 @@ +import { Notification, VSBrowser, Workbench } from 'vscode-extension-tester'; + +/** Dismisses every currently-visible toast notification (best effort). */ +export async function dismissAllNotifications(): Promise { + const notifications = await new Workbench().getNotifications().catch(() => [] as Notification[]); + for (const notification of notifications) { + await notification.dismiss().catch(() => undefined); + } +} + +/** + * Waits for a toast notification whose message matches `pattern`. When `required` is false a missed + * (or auto-dismissed) notification resolves to `undefined` instead of throwing — useful for + * transient success toasts where the authoritative gate is elsewhere. + */ +export async function waitForNotification( + pattern: RegExp, + timeout: number, + required: boolean +): Promise { + const driver = VSBrowser.instance.driver; + + try { + return (await driver.wait( + async () => { + const notifications = await new Workbench().getNotifications().catch(() => [] as Notification[]); + for (const notification of notifications) { + const message = await notification.getMessage().catch(() => ''); + if (pattern.test(message)) { + return notification; + } + } + + return undefined; + }, + timeout, + `timed out waiting for a notification matching ${pattern}` + )) as Notification; + } catch (error) { + if (required) { + throw error; + } + + return undefined; + } +} diff --git a/test/e2e/helpers/quickInput.ts b/test/e2e/helpers/quickInput.ts new file mode 100644 index 0000000000..02a2911be9 --- /dev/null +++ b/test/e2e/helpers/quickInput.ts @@ -0,0 +1,37 @@ +import { By, InputBox, VSBrowser } from 'vscode-extension-tester'; + +/** + * Tries to open the active InputBox/QuickPick, returning `undefined` instead of throwing when none + * appears within `timeout`. Useful when a command may either open a quick pick or bail with a + * notification. + */ +export async function tryOpenInputBox(timeout: number): Promise { + try { + return await InputBox.create(timeout); + } catch { + return undefined; + } +} + +/** + * Clicks the "OK" button of the in-window simple file/folder dialog + * (`files.simpleDialog.enable`). In that dialog Enter navigates *into* a directory rather than + * accepting it, so clicking OK is the deterministic accept. Returns false if no OK button is found. + */ +export async function clickDialogOkButton(): Promise { + const driver = VSBrowser.instance.driver; + const buttons = await driver + .findElements(By.css('.quick-input-widget .monaco-button.monaco-text-button')) + .catch(() => []); + + for (const button of buttons) { + const text = (await button.getText().catch(() => '')).trim(); + if (text === 'OK') { + await button.click(); + + return true; + } + } + + return false; +} diff --git a/test/e2e/helpers/workspace.ts b/test/e2e/helpers/workspace.ts new file mode 100644 index 0000000000..9f4b22dce7 --- /dev/null +++ b/test/e2e/helpers/workspace.ts @@ -0,0 +1,84 @@ +import { By, InputBox, VSBrowser, Workbench } from 'vscode-extension-tester'; + +import { DIALOG_RESOLVE_DELAY, FOLDER_OPEN_ATTEMPTS, FOLDER_RELOAD_TIMEOUT, QUICK_PICK_TIMEOUT } from './constants'; +import { clickDialogOkButton } from './quickInput'; + +/** + * Opens a file that lives in the currently-open workspace folder via Quick Open ("Go to File..."), + * matching by file name. Unlike the simple Open File dialog (where Enter does not accept a typed + * path), Quick Open reliably opens the highlighted match on confirm. + * + * Driving the running window directly avoids ExTester's `openResources`, which shells out to + * `code -r ` (reuse-window over IPC) and silently no-ops in a sandboxed/headless instance. + */ +export async function openWorkspaceFile(fileName: string): Promise { + const driver = VSBrowser.instance.driver; + + await new Workbench().executeCommand('Go to File...'); + + const quickOpen = await InputBox.create(QUICK_PICK_TIMEOUT); + await quickOpen.setText(fileName); + await driver.wait( + async () => (await quickOpen.getQuickPicks()).length > 0, + QUICK_PICK_TIMEOUT, + `"${fileName}" did not appear in Quick Open` + ); + await quickOpen.confirm(); +} + +/** + * Opens an absolute folder path as the workspace root via "File: Open Folder...". Opening a folder + * reloads the VS Code window. In the simple folder dialog, Enter navigates *into* a directory rather + * than accepting it as the workspace — the deterministic accept is the dialog's "OK" button — so we + * type the path, click OK, and wait for the pre-reload workbench element to detach (reload started). + * We retry the whole interaction defensively. The caller then waits for the new workbench to mount. + */ +export async function openFolderViaDialog(folder: string): Promise { + const driver = VSBrowser.instance.driver; + + for (let attempt = 1; attempt <= FOLDER_OPEN_ATTEMPTS; attempt++) { + const previousWorkbench = await driver.findElement(By.css('.monaco-workbench')); + + await new Workbench().executeCommand('File: Open Folder...'); + const dialog = await InputBox.create(QUICK_PICK_TIMEOUT); + await dialog.setText(folder); + + // The simple dialog resolves the typed path asynchronously (listing the enclosing + // directory); wait for that listing and add a short settle before accepting. + await driver + .wait( + async () => (await dialog.getQuickPicks()).length > 0, + QUICK_PICK_TIMEOUT, + 'dialog did not resolve path' + ) + .catch(() => undefined); + await driver.sleep(DIALOG_RESOLVE_DELAY); + + const accepted = await clickDialogOkButton(); + if (!accepted) { + await new InputBox().cancel().catch(() => undefined); + continue; + } + + const reloaded = await driver + .wait(async () => { + try { + await previousWorkbench.getTagName(); + + return false; + } catch { + return true; + } + }, FOLDER_RELOAD_TIMEOUT) + .then(() => true) + .catch(() => false); + if (reloaded) { + return; + } + + // The folder did not open this time; dismiss any lingering dialog and retry. + await new InputBox().cancel().catch(() => undefined); + } + + throw new Error(`Failed to open folder "${folder}" after ${FOLDER_OPEN_ATTEMPTS} attempts`); +} diff --git a/test/e2e/suite/helloWorld.e2e.test.ts b/test/e2e/suite/helloWorld.e2e.test.ts index 3d3fdb4777..a4550d199d 100644 --- a/test/e2e/suite/helloWorld.e2e.test.ts +++ b/test/e2e/suite/helloWorld.e2e.test.ts @@ -9,79 +9,44 @@ * 4. run the cell (the notebook toolbar's "Run All" button) * 5. assert the rendered stdout output contains "hello world" * - * Prerequisites (see specs/e2e-extester-testing-plan.md): + * The reusable interaction helpers live in `test/e2e/helpers/`; this file is only the suite wiring. + * + * Prerequisites: * - The Python extension (`ms-python.python`) must be installed in the test instance * (`npm run setup:e2e:deps`) and at least one Python interpreter must be discoverable. - * - Creating the environment provisions a venv and the Deepnote toolkit, which needs - * network access; the first kernel start can take a few minutes. - * - * Notebook output in VS Code renders inside two nested iframes - * (iframe.webview.ready -> #active-frame). ExTester's WebView page object descends - * exactly those two levels, which is how we read the rendered output below. + * - Creating the environment provisions a venv and the Deepnote toolkit, which needs network + * access; the first kernel start can take a few minutes. */ -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; - import { expect } from 'chai'; -import { - By, - EditorView, - InputBox, - Notification, - VSBrowser, - WebView, - Workbench, - type WebDriver -} from 'vscode-extension-tester'; +import { EditorView, VSBrowser, WebView } from 'vscode-extension-tester'; -// Command palette labels (category + title) the way `Workbench.executeCommand` matches them. -const CREATE_ENV_COMMAND = 'Deepnote: Create Environment'; -const SELECT_ENV_COMMAND = 'Deepnote: Select Environment for Notebook'; +import { + OUTPUT_TIMEOUT, + WORKBENCH_TIMEOUT, + copyFixtureToTempDir, + createEnvironment, + openFolderViaDialog, + openWorkspaceFile, + runAndAwaitOutput, + selectEnvironmentForNotebook +} from '../helpers'; const NOTEBOOK_FILE_NAME = 'hello-world.deepnote'; const EXPECTED_OUTPUT = 'hello world'; -// Timeouts (ms). UI ops are slow and the first kernel start is the slowest step. -const WORKBENCH_TIMEOUT = 60_000; -const QUICK_PICK_TIMEOUT = 30_000; -const ENV_CREATED_TIMEOUT = 120_000; -const KERNEL_CONNECT_TIMEOUT = 300_000; -const OUTPUT_TIMEOUT = 300_000; -// How often to re-issue "Run All" while waiting for output — the first run can be dropped right -// after the kernel connects. -const RUN_ALL_REISSUE_INTERVAL = 25_000; -const INTERPRETER_RETRY_DELAY = 5_000; -const MAX_CREATE_ATTEMPTS = 6; -// The in-window simple file/folder dialog needs a beat to resolve a typed path before it accepts. -const DIALOG_RESOLVE_DELAY = 1_500; -const FOLDER_OPEN_ATTEMPTS = 5; -const FOLDER_RELOAD_TIMEOUT = 12_000; - -// Selectors that only exist inside the notebook output iframe (`#active-frame`), -// so reading them cannot accidentally match the cell's source in the editor. -const OUTPUT_SELECTOR = '.output_container, .output, .rendered-output'; - describe('Deepnote E2E — run "hello world"', function () { // Per-test timeout for the whole suite (overrides the mocharc default for these tests). this.timeout(22 * 60 * 1000); - let driver: WebDriver; - let notebookFile: string; // A stable name: createEnvironment is idempotent (it treats "already exists" as success), so a // leftover environment from a previous or retried run is reused rather than colliding — which // also lets a persistent test instance reuse the already-provisioned venv. const environmentName = 'E2E Hello Env'; before(async function () { - driver = VSBrowser.instance.driver; - - // Open a throwaway copy so execution-dirtied notebook state never touches the source tree. - const source = path.resolve(process.cwd(), 'test', 'e2e', 'fixtures', NOTEBOOK_FILE_NAME); - const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'deepnote-e2e-')); - notebookFile = path.join(tempDir, NOTEBOOK_FILE_NAME); - fs.copyFileSync(source, notebookFile); + // Work on a throwaway copy so execution-dirtied notebook state never touches the source tree. + const { tempDir } = copyFixtureToTempDir(NOTEBOOK_FILE_NAME); await VSBrowser.instance.waitForWorkbench(WORKBENCH_TIMEOUT); @@ -94,15 +59,12 @@ describe('Deepnote E2E — run "hello world"', function () { await openFolderViaDialog(tempDir); await VSBrowser.instance.waitForWorkbench(WORKBENCH_TIMEOUT); - // Open the notebook by driving the running window directly. ExTester's `openResources` - // shells out to `code -r ` (reuse-window over IPC), which silently no-ops in a - // sandboxed/headless environment. Now that the containing folder is the workspace, the - // notebook is reachable by name through Quick Open ("Go to File..."). + // Now that the containing folder is the workspace, the notebook is reachable by name. await openWorkspaceFile(NOTEBOOK_FILE_NAME); - // The native notebook editor opens because the extension registers a serializer for - // the `deepnote` notebook type; a single-notebook file resolves to its default notebook. - await driver.wait( + // The native notebook editor opens because the extension registers a serializer for the + // `deepnote` notebook type; a single-notebook file resolves to its default notebook. + await VSBrowser.instance.driver.wait( async () => (await new EditorView().getOpenEditorTitles()).some((t) => t.includes(NOTEBOOK_FILE_NAME)), WORKBENCH_TIMEOUT, 'Deepnote notebook editor did not open' @@ -117,323 +79,9 @@ describe('Deepnote E2E — run "hello world"', function () { it('creates an environment, connects the kernel, runs the cell and renders output', async function () { await createEnvironment(environmentName); - await selectEnvironmentForNotebook(environmentName); + await selectEnvironmentForNotebook(environmentName, NOTEBOOK_FILE_NAME); - const renderedOutput = await runAndAwaitOutput(EXPECTED_OUTPUT, OUTPUT_TIMEOUT); + const renderedOutput = await runAndAwaitOutput(NOTEBOOK_FILE_NAME, EXPECTED_OUTPUT, OUTPUT_TIMEOUT); expect(renderedOutput).to.contain(EXPECTED_OUTPUT); }); - - /** - * Drives `deepnote.environments.create`: pick interpreter -> name -> skip packages -> - * skip description. Retries when the Python extension has not finished discovering an - * interpreter yet (the command shows an error and returns instead of opening a quick pick). - */ - async function createEnvironment(name: string): Promise { - let lastError: unknown; - - for (let attempt = 1; attempt <= MAX_CREATE_ATTEMPTS; attempt++) { - await new Workbench().executeCommand(CREATE_ENV_COMMAND); - - // Either the interpreter quick pick opens, or (no interpreter discovered yet) the - // command shows a "No Python interpreters found" notification and returns. - const interpreterPick = await tryOpenInputBox(5_000); - if (!interpreterPick) { - await dismissAllNotifications(); - await driver.sleep(INTERPRETER_RETRY_DELAY); - lastError = new Error('interpreter quick pick did not appear (interpreter discovery not ready?)'); - continue; - } - - try { - await driver.wait( - async () => (await interpreterPick.getQuickPicks()).length > 0, - QUICK_PICK_TIMEOUT, - 'no Python interpreters were listed' - ); - } catch (error) { - await interpreterPick.cancel().catch(() => undefined); - await dismissAllNotifications(); - await driver.sleep(INTERPRETER_RETRY_DELAY); - lastError = error; - continue; - } - - await interpreterPick.selectQuickPick(0); - - const nameBox = await InputBox.create(); - await nameBox.setText(name); - await nameBox.confirm(); - - // Packages (optional) — leave empty. - await (await InputBox.create()).confirm(); - - // Description (optional) — leave empty. - await (await InputBox.create()).confirm(); - - // Treat both the success toast and the "already exists" guard as success: a leftover - // environment from a previous/retried run is fine — it will be selected next. - await waitForNotification(/created successfully|already exists/i, ENV_CREATED_TIMEOUT, false); - return; - } - - throw new Error( - `Failed to create a Deepnote environment after ${MAX_CREATE_ATTEMPTS} attempts. ` + - `Ensure the Python extension is installed and an interpreter is discoverable. ` + - `Last error: ${String(lastError)}` - ); - } - - /** - * Drives `deepnote.environments.selectForNotebook`. Selecting the environment rebuilds and - * explicitly selects the notebook's kernel controller (provisioning the venv + toolkit), - * which is what "wait for the kernel to connect" means in this extension. - */ - async function selectEnvironmentForNotebook(name: string): Promise { - // The command requires an active `deepnote` notebook — make sure it's focused. - await new EditorView().openEditor(NOTEBOOK_FILE_NAME); - - // Clear the "select an environment" prompt and any other toasts; they can overlap the - // quick pick and intercept clicks. - await dismissAllNotifications(); - - await new Workbench().executeCommand(SELECT_ENV_COMMAND); - - const environmentPick = await InputBox.create(QUICK_PICK_TIMEOUT); - // Filter to the environment by name and accept with Enter rather than clicking the row: - // the quick-pick row contains a description `

` that can intercept a positional click. - await environmentPick.setText(name); - await driver.wait( - async () => (await environmentPick.getQuickPicks()).length > 0, - QUICK_PICK_TIMEOUT, - 'environment quick pick was empty' - ); - await environmentPick.confirm(); - - // Best-effort wait for the "switched successfully" toast; the authoritative gate is the - // rendered output below, so a missed (auto-dismissed) toast must not fail the test. - await waitForNotification(/switched successfully/i, KERNEL_CONNECT_TIMEOUT, false); - } - - /** - * Clicks the notebook editor toolbar's "Run All" button. The command-palette entry for - * `deepnote.runallcells` ("Jupyter: Run All Cells") is gated behind context keys - * (`deepnote.ispythonornativeactive`, …) that are not reliably set under automation, so driving - * it through `Workbench.executeCommand` can silently miss and trigger the wrong command. - */ - async function clickRunAll(): Promise { - await new EditorView().openEditor(NOTEBOOK_FILE_NAME); - - const runAllButton = await driver.wait( - async () => { - const [button] = await driver.findElements(By.css('a.action-label[aria-label="Run All"]')); - - return button; - }, - WORKBENCH_TIMEOUT, - 'notebook "Run All" button did not appear' - ); - await runAllButton.click(); - } - - /** - * Opens a file that lives in the currently-open workspace folder via Quick Open ("Go to - * File..."), matching by file name. Unlike the simple Open File dialog (where Enter does not - * accept a typed path), Quick Open reliably opens the highlighted match on confirm. - */ - async function openWorkspaceFile(fileName: string): Promise { - await new Workbench().executeCommand('Go to File...'); - - const quickOpen = await InputBox.create(QUICK_PICK_TIMEOUT); - await quickOpen.setText(fileName); - await driver.wait( - async () => (await quickOpen.getQuickPicks()).length > 0, - QUICK_PICK_TIMEOUT, - `"${fileName}" did not appear in Quick Open` - ); - await quickOpen.confirm(); - } - - /** - * Opens an absolute folder path as the workspace root via "File: Open Folder...". Opening a - * folder reloads the VS Code window. In the simple folder dialog, Enter navigates *into* a - * directory rather than accepting it as the workspace — the deterministic accept is the dialog's - * "OK" button — so we type the path, click OK, and wait for the pre-reload workbench element to - * detach (reload started). We retry the whole interaction defensively. The caller then waits for - * the new workbench to mount. - */ - async function openFolderViaDialog(folder: string): Promise { - for (let attempt = 1; attempt <= FOLDER_OPEN_ATTEMPTS; attempt++) { - const previousWorkbench = await driver.findElement(By.css('.monaco-workbench')); - - await new Workbench().executeCommand('File: Open Folder...'); - const dialog = await InputBox.create(QUICK_PICK_TIMEOUT); - await dialog.setText(folder); - - // The simple dialog resolves the typed path asynchronously (listing the enclosing - // directory); wait for that listing and add a short settle before accepting. - await driver - .wait( - async () => (await dialog.getQuickPicks()).length > 0, - QUICK_PICK_TIMEOUT, - 'dialog did not resolve path' - ) - .catch(() => undefined); - await driver.sleep(DIALOG_RESOLVE_DELAY); - - const accepted = await clickDialogOkButton(); - if (!accepted) { - await new InputBox().cancel().catch(() => undefined); - continue; - } - - const reloaded = await driver - .wait(async () => { - try { - await previousWorkbench.getTagName(); - - return false; - } catch { - return true; - } - }, FOLDER_RELOAD_TIMEOUT) - .then(() => true) - .catch(() => false); - if (reloaded) { - return; - } - - // The folder did not open this time; dismiss any lingering dialog and retry. - await new InputBox().cancel().catch(() => undefined); - } - - throw new Error(`Failed to open folder "${folder}" after ${FOLDER_OPEN_ATTEMPTS} attempts`); - } - - /** Clicks the simple file/folder dialog's "OK" button. Returns false if it could not be found. */ - async function clickDialogOkButton(): Promise { - const buttons = await driver - .findElements(By.css('.quick-input-widget .monaco-button.monaco-text-button')) - .catch(() => []); - for (const button of buttons) { - const text = (await button.getText().catch(() => '')).trim(); - if (text === 'OK') { - await button.click(); - - return true; - } - } - - return false; - } - - /** - * Clicks "Run All" and polls the notebook output webview until the expected text renders, - * re-issuing "Run All" periodically. The first run can be dropped when the kernel has only just - * finished connecting, so we keep nudging it until output appears (re-running `print(...)` is - * harmless). - */ - async function runAndAwaitOutput(expected: string, timeout: number): Promise { - const deadline = Date.now() + timeout; - let lastRunAt = 0; - let lastText = ''; - - while (Date.now() < deadline) { - if (Date.now() - lastRunAt > RUN_ALL_REISSUE_INTERVAL) { - await clickRunAll().catch(() => undefined); - lastRunAt = Date.now(); - } - - lastText = await readRenderedOutput(); - if (lastText.includes(expected)) { - return lastText; - } - - await driver.sleep(2_000); - } - - throw new Error( - `Timed out after ${timeout}ms waiting for rendered output to contain "${expected}". ` + - `Last observed output: ${JSON.stringify(lastText)}` - ); - } - - /** - * Reads the notebook cell output once. - * - * Output lives two iframes deep (iframe.webview.ready -> #active-frame). We only attempt to - * switch when an output webview iframe actually exists (`getViewToSwitchTo`), and we read - * output-specific elements inside the frame — so we never match the cell's source code that - * is visible in the editor of the main document. Returns '' when no output is present yet. - */ - async function readRenderedOutput(): Promise { - const webView = new WebView(); - const outputFrame = await webView.getViewToSwitchTo().catch(() => undefined); - if (!outputFrame) { - return ''; - } - - let text = ''; - try { - await webView.switchToFrame(5_000); - const elements = await webView.findWebElements(By.css(OUTPUT_SELECTOR)); - const texts = await Promise.all(elements.map((element) => element.getText().catch(() => ''))); - text = texts.join('\n').trim(); - - // Fallback: if the renderer used unexpected classes, read the frame body — safe here - // because we have confirmed we are inside the output iframe, not the editor. - if (!text) { - const body = await webView.findWebElement(By.css('body')).catch(() => undefined); - text = body ? (await body.getText().catch(() => '')).trim() : ''; - } - } catch { - // Frame went stale or output not painted yet — treat as no output this tick. - } finally { - await webView.switchBack().catch(() => undefined); - } - - return text; - } - - async function tryOpenInputBox(timeout: number): Promise { - try { - return await InputBox.create(timeout); - } catch { - return undefined; - } - } - - async function dismissAllNotifications(): Promise { - const notifications = await new Workbench().getNotifications().catch(() => [] as Notification[]); - for (const notification of notifications) { - await notification.dismiss().catch(() => undefined); - } - } - - async function waitForNotification( - pattern: RegExp, - timeout: number, - required: boolean - ): Promise { - try { - return (await driver.wait( - async () => { - const notifications = await new Workbench().getNotifications().catch(() => [] as Notification[]); - for (const notification of notifications) { - const message = await notification.getMessage().catch(() => ''); - if (pattern.test(message)) { - return notification; - } - } - return undefined; - }, - timeout, - `timed out waiting for a notification matching ${pattern}` - )) as Notification; - } catch (error) { - if (required) { - throw error; - } - return undefined; - } - } }); From a7892bcb0834814e0007b9efa700ad9cf73f44e7 Mon Sep 17 00:00:00 2001 From: tomas Date: Thu, 25 Jun 2026 14:16:19 +0000 Subject: [PATCH 7/7] ci: add E2E workflow that runs after CD using the built VSIX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New .github/workflows/e2e.yml triggers via workflow_run after the CD workflow, downloads the extension VSIX that CD already built and uploaded, installs it into the test VS Code (extest install-vsix), and runs the ExTester suite against it — so the extension is built once (in CD) rather than twice. Adds a test:e2e:prebuilt script (extest run-tests) that runs the suite against an already-installed extension without repackaging; the workflow uses it after install-vsix. Locally verified: install-vsix + test:e2e:prebuilt passes against a prebuilt VSIX. workflow_run workflows fire from the default branch, so this takes effect once on main; a workflow_dispatch trigger is included for manual runs. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01LQk7n13UfmeDo2ujy8X4LX --- .github/workflows/e2e.yml | 126 ++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 127 insertions(+) create mode 100644 .github/workflows/e2e.yml diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml new file mode 100644 index 0000000000..4fda419f4a --- /dev/null +++ b/.github/workflows/e2e.yml @@ -0,0 +1,126 @@ +name: E2E + +# Runs the ExTester end-to-end suite against the extension VSIX produced by the CD workflow, so the +# extension is built only once (in CD) and merely installed + exercised here. +on: + workflow_run: + workflows: [CD] + types: [completed] + workflow_dispatch: + +permissions: + contents: read + actions: read # required to download the VSIX artifact from the triggering CD run + +concurrency: + group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch || github.ref }} + cancel-in-progress: true + +jobs: + e2e: + name: E2E (ExTester) + runs-on: ubuntu-latest + timeout-minutes: 45 + # Only run after a *successful* CD (a failed CD produces no VSIX). Manual runs always proceed. + if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} + env: + # Keep ExTester's downloads (test VS Code, ChromeDriver, settings, screenshots) inside the + # workspace so the artifact-upload paths are predictable. Both this and .test-extensions are + # gitignored. + TEST_RESOURCES: ${{ github.workspace }}/test-resources + steps: + - name: Checkout (the commit CD built) + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + ref: ${{ github.event.workflow_run.head_sha || github.sha }} + + - name: Setup Node.js + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 + with: + cache: 'npm' + node-version-file: '.nvmrc' + + - name: Setup Python # interpreter the Deepnote environment is created from + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: '3.12' + + - name: Install dependencies + run: npm ci --prefer-offline --no-audit + + - name: Compile the E2E test sources + run: npm run compile-e2e + + - name: Install Electron/Chromium runtime libraries + Xvfb + run: | + sudo apt-get update + sudo apt-get install -y xvfb \ + libatk1.0-0t64 libatk-bridge2.0-0t64 libcups2t64 libgtk-3-0t64 libgdk-pixbuf-2.0-0 \ + libgbm1 libasound2t64 libnss3 libnspr4 libxss1 libxshmfence1 libdrm2 libxkbcommon0 \ + libxcomposite1 libxdamage1 libxrandr2 libxfixes3 libxext6 libxrender1 libpango-1.0-0 \ + libcairo2 libatspi2.0-0 libx11-xcb1 libxcb-dri3-0 libxtst6 libsecret-1-0 \ + libgssapi-krb5-2 libdbus-1-3 libexpat1 python3.12-venv python3-pip + + - name: Resolve the CD run that built the VSIX + id: cd + env: + GH_TOKEN: ${{ github.token }} + run: | + if [ "${{ github.event_name }}" = "workflow_run" ]; then + RUN_ID="${{ github.event.workflow_run.id }}" + else + # Manual run: fall back to the most recent successful CD run. + RUN_ID=$(gh run list --workflow cd.yml --status success --limit 1 --json databaseId --jq '.[0].databaseId') + fi + test -n "$RUN_ID" + echo "run_id=$RUN_ID" >> "$GITHUB_OUTPUT" + echo "Using CD run id: $RUN_ID" + + - name: Download the extension VSIX built by CD + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + with: + run-id: ${{ steps.cd.outputs.run_id }} + github-token: ${{ github.token }} + path: ./vsix + merge-multiple: true + + - name: Locate the VSIX + id: vsix + run: | + FILE=$(ls ./vsix/vscode-deepnote-*.vsix | head -1) + test -f "$FILE" + echo "path=$FILE" >> "$GITHUB_OUTPUT" + echo "Testing VSIX: $FILE" + + - name: Download the test VS Code + ChromeDriver + run: npm run setup:e2e:vscode + + - name: Install the CD-built extension into the test instance (no rebuild) + run: npx extest install-vsix -f "${{ steps.vsix.outputs.path }}" -e .test-extensions + + - name: Install the Python extension into the test instance + run: npm run setup:e2e:deps + + - name: Run E2E + # ExTester launches VS Code with --no-sandbox by default, so no AppArmor sysctl is needed. + # A small retry absorbs transient UI/launch flakiness (mocha already retries the test once). + run: | + attempt=1 + max=2 + until xvfb-run --auto-servernum --server-args='-screen 0 1920x1080x24' npm run test:e2e:prebuilt; do + if [ "$attempt" -ge "$max" ]; then + echo "E2E failed after $attempt attempt(s)" + exit 1 + fi + echo "E2E attempt $attempt failed — retrying…" + attempt=$((attempt + 1)) + done + + - name: Upload failure screenshots + if: failure() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: e2e-screenshots + path: ${{ env.TEST_RESOURCES }}/screenshots/**/*.png + if-no-files-found: ignore + retention-days: 14 diff --git a/package.json b/package.json index fb1ffcff2e..fc1c6d8de7 100644 --- a/package.json +++ b/package.json @@ -2678,6 +2678,7 @@ "setup:e2e:deps": "extest install-from-marketplace ms-python.python -e .test-extensions", "setup:e2e": "npm run setup:e2e:vscode && npm run setup:e2e:deps", "test:e2e": "extest setup-and-run \"./out/e2e/suite/*.e2e.test.js\" -c max -o ./test/e2e/settings.json -e .test-extensions -m ./test/e2e/.mocharc.js -i", + "test:e2e:prebuilt": "extest run-tests \"./out/e2e/suite/*.e2e.test.js\" -c max -o ./test/e2e/settings.json -e .test-extensions -m ./test/e2e/.mocharc.js", "test:unittests": "mocha --config ./build/.mocha.unittests.js.json ./out/**/*.unit.test.js", "test": "npm run test:unittests", "typecheck": "tsc -p ./ --noEmit",