From 4015fd90c6a2a889a6eff66d03bcc6a39b4500d3 Mon Sep 17 00:00:00 2001 From: tomas Date: Wed, 24 Jun 2026 06:45:55 +0000 Subject: [PATCH 01/15] 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 02/15] 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 03/15] 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 04/15] 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 05/15] 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 06/15] 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 07/15] 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", From 8d649c238d3eb28c215a2d348af9cf4f7acc2839 Mon Sep 17 00:00:00 2001 From: tomas Date: Fri, 26 Jun 2026 07:13:01 +0000 Subject: [PATCH 08/15] ci(e2e): build the VSIX in-workflow and trigger like CI Replace the workflow_run-on-CD trigger with push/pull_request to main plus workflow_dispatch, matching the CI workflow. Package the extension here via `npm run package` (with the Tailwind native-module workaround and a global vsce install, same as CD) instead of resolving and downloading the VSIX artifact from the triggering CD run. This makes the E2E suite self-contained and PR-gating, dropping the CD-coupling steps and the actions:read permission they required. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_014SefBqWeg8vf7jQ6vDhGU3 --- .github/workflows/e2e.yml | 69 +++++++++++++-------------------------- 1 file changed, 23 insertions(+), 46 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 4fda419f4a..143a28bbd1 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -1,19 +1,20 @@ 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. +# Builds the extension VSIX and runs the ExTester end-to-end suite against it. The VSIX is packaged +# here (same as CD) rather than downloaded from CD, so this workflow is self-contained and triggers +# on the same events as CI. on: - workflow_run: - workflows: [CD] - types: [completed] + push: + branches: [main] + pull_request: + branches: [main] 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 }} + group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: @@ -21,18 +22,14 @@ jobs: 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) + - name: Checkout 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 @@ -46,7 +43,18 @@ jobs: python-version: '3.12' - name: Install dependencies - run: npm ci --prefer-offline --no-audit + run: | + npm ci --prefer-offline --no-audit + # Verify Tailwind CSS native modules are installed for Linux (npm optional deps bug) + # See: https://github.com/npm/cli/issues/4828 + node -e "try { require('lightningcss'); } catch { process.exit(1); }" 2>/dev/null || npm install lightningcss-linux-x64-gnu + node -e "try { require('@tailwindcss/oxide'); } catch { process.exit(1); }" 2>/dev/null || npm install @tailwindcss/oxide-linux-x64-gnu + + - name: Install vsce + run: npm install -g @vscode/vsce + + - name: Package extension + run: npm run package - name: Compile the E2E test sources run: npm run compile-e2e @@ -61,42 +69,11 @@ jobs: 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 built extension into the test instance + run: npx extest install-vsix -f vscode-deepnote-insiders.vsix -e .test-extensions - name: Install the Python extension into the test instance run: npm run setup:e2e:deps From ac96fae2550ea98261ec887310e64d62695d5c4e Mon Sep 17 00:00:00 2001 From: tomas Date: Fri, 26 Jun 2026 08:23:08 +0000 Subject: [PATCH 09/15] ci(e2e): cache pip wheel downloads to speed up env provisioning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The single E2E test spends ~4m30s of its ~5m provisioning the Deepnote environment, which pip-installs the toolkit dependency tree (deepnote-toolkit[server], ipykernel, python-lsp-server[all], deepnote-cli) into a fresh venv — cold from PyPI on every run. Cache ~/.cache/pip so those wheels are reused across runs. The installs already use pip's cache (nothing passes --no-cache-dir). The key busts when the toolkit version / install set changes; the restore-keys prefix keeps the cache warm across unrelated changes since pip's cache is additive. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_014SefBqWeg8vf7jQ6vDhGU3 --- .github/workflows/e2e.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 143a28bbd1..ceb6a08b3e 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -78,6 +78,20 @@ jobs: - name: Install the Python extension into the test instance run: npm run setup:e2e:deps + - name: Cache pip wheel downloads + # Provisioning the Deepnote environment pip-installs the toolkit dependency tree into a + # fresh venv on first kernel connect — the bulk of the E2E runtime. Caching pip's wheel + # cache makes that warm on later runs (the installs use the cache; nothing passes + # --no-cache-dir). The key busts when the toolkit version / install set changes; the + # restore-keys prefix keeps the cache warm across unrelated changes, since pip's cache is + # additive. + uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0 + with: + path: ~/.cache/pip + key: pip-${{ runner.os }}-py312-${{ hashFiles('src/kernels/deepnote/types.ts', 'src/kernels/deepnote/deepnoteToolkitInstaller.node.ts') }} + restore-keys: | + pip-${{ runner.os }}-py312- + - 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). From 83bbe6029a5f2c2abc74bb277421e96d9f7cc083 Mon Sep 17 00:00:00 2001 From: tomas Date: Fri, 26 Jun 2026 11:54:06 +0000 Subject: [PATCH 10/15] fix(deepnote): bind kernel via NotebookEditor after env switch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ensureControllerSelectedForNotebook passed the NotebookDocument as the notebook.selectKernel `notebookEditor` argument, which can fail to bind the controller to the notebook — leaving it without an executable kernel so the first cell-execution requests are silently dropped until VS Code happens to settle. The placeholder path already resolves a real NotebookEditor via findNotebookEditor (with a comment noting it is required); apply the same here, falling back to the document. This removes a 40-106s stall observed before the first cell runs after selecting an environment (kernel now binds in ~0.5s), benefiting both real users and the E2E suite. Also harden the E2E run loop: drop RUN_ALL_REISSUE_INTERVAL 25s -> 5s (and extract OUTPUT_POLL_INTERVAL) so that if a run is ever still dropped, recovery is fast instead of up to a full 25s per miss. Measured locally (built VSIX + xvfb + ms-python), the hello-world E2E test body dropped from ~225s to ~88-107s across 5 runs. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_014SefBqWeg8vf7jQ6vDhGU3 --- .../deepnote/deepnoteKernelAutoSelector.node.ts | 8 +++++++- test/e2e/helpers/constants.ts | 11 ++++++++--- test/e2e/helpers/notebook.ts | 4 ++-- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/notebooks/deepnote/deepnoteKernelAutoSelector.node.ts b/src/notebooks/deepnote/deepnoteKernelAutoSelector.node.ts index 63e7129200..a3b1fa799e 100644 --- a/src/notebooks/deepnote/deepnoteKernelAutoSelector.node.ts +++ b/src/notebooks/deepnote/deepnoteKernelAutoSelector.node.ts @@ -737,8 +737,14 @@ export class DeepnoteKernelAutoSelector implements IDeepnoteKernelAutoSelector, controller.controller.updateNotebookAffinity(notebook, NotebookControllerAffinity.Preferred); + // notebook.selectKernel needs a NotebookEditor (see findNotebookEditor). Passing the + // NotebookDocument can fail to bind the controller to the notebook, leaving it without an + // executable kernel so the first execution requests are silently dropped until VS Code + // happens to settle — observed as a multi-minute stall before the first cell runs. + const notebookEditor = await this.findNotebookEditor(notebook); + await commands.executeCommand('notebook.selectKernel', { - notebookEditor: notebook, + notebookEditor: notebookEditor ?? notebook, id: controller.connection.id, // id: controller.controller.id, extension: JVSC_EXTENSION_ID diff --git a/test/e2e/helpers/constants.ts b/test/e2e/helpers/constants.ts index 900b544367..b4303f3f09 100644 --- a/test/e2e/helpers/constants.ts +++ b/test/e2e/helpers/constants.ts @@ -7,9 +7,14 @@ 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; +// How often to re-issue "Run All" while waiting for output. VS Code drops the first run request(s) +// while the kernel is still connecting, so we keep nudging it; a short interval makes recovery from +// a dropped run fast (a coarse interval can add a full interval's delay per dropped run — observed +// adding ~100s with a 25s value). +export const RUN_ALL_REISSUE_INTERVAL = 5_000; + +// How often to poll the output webview for the expected text. +export const OUTPUT_POLL_INTERVAL = 1_500; export const INTERPRETER_RETRY_DELAY = 5_000; export const MAX_CREATE_ATTEMPTS = 6; diff --git a/test/e2e/helpers/notebook.ts b/test/e2e/helpers/notebook.ts index 7420a60a6d..1782974f68 100644 --- a/test/e2e/helpers/notebook.ts +++ b/test/e2e/helpers/notebook.ts @@ -1,6 +1,6 @@ import { By, EditorView, VSBrowser, WebView } from 'vscode-extension-tester'; -import { OUTPUT_SELECTOR, RUN_ALL_REISSUE_INTERVAL, WORKBENCH_TIMEOUT } from './constants'; +import { OUTPUT_POLL_INTERVAL, 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 @@ -84,7 +84,7 @@ export async function runAndAwaitOutput(notebookFileName: string, expected: stri return lastText; } - await driver.sleep(2_000); + await driver.sleep(OUTPUT_POLL_INTERVAL); } throw new Error( From 50895153a862677e4bf675c0bc7459d28e2c56e0 Mon Sep 17 00:00:00 2001 From: tomas Date: Fri, 26 Jun 2026 15:56:19 +0000 Subject: [PATCH 11/15] fix(e2e): drive optional env prompts only when they appear MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit createEnvironment unconditionally drove the packages and description input boxes after confirming the name. When the environment name already exists, `deepnote.environments.create` short-circuits after the name prompt with an "already exists" notification and opens no further inputs, so those `InputBox.create()` calls timed out and threw before the helper reached its `/already exists/` success check — breaking the documented idempotent retry path (Mocha `retries: 1` plus the workflow retry loop) on any rerun with a leftover `E2E Hello Env`. Guard the optional prompts behind `tryOpenInputBox` so they are driven only when the packages box actually appears; otherwise fall through to the existing notification check, which matches the sticky "already exists" toast and reuses the environment. Adds OPTIONAL_PROMPT_TIMEOUT (5s, matching the previous implicit InputBox.create default). Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_014SefBqWeg8vf7jQ6vDhGU3 --- test/e2e/helpers/constants.ts | 5 +++++ test/e2e/helpers/deepnoteEnvironment.ts | 19 ++++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/test/e2e/helpers/constants.ts b/test/e2e/helpers/constants.ts index b4303f3f09..96a0df7d74 100644 --- a/test/e2e/helpers/constants.ts +++ b/test/e2e/helpers/constants.ts @@ -19,6 +19,11 @@ export const OUTPUT_POLL_INTERVAL = 1_500; export const INTERPRETER_RETRY_DELAY = 5_000; export const MAX_CREATE_ATTEMPTS = 6; +// How long to wait for an optional input box (packages/description) to appear after confirming the +// environment name. When the name already exists the create command short-circuits with an "already +// exists" notification and opens no further inputs, so this wait elapses and the prompts are skipped. +export const OPTIONAL_PROMPT_TIMEOUT = 5_000; + // 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; diff --git a/test/e2e/helpers/deepnoteEnvironment.ts b/test/e2e/helpers/deepnoteEnvironment.ts index 3eaa7a044d..5b8e5863ac 100644 --- a/test/e2e/helpers/deepnoteEnvironment.ts +++ b/test/e2e/helpers/deepnoteEnvironment.ts @@ -5,6 +5,7 @@ import { INTERPRETER_RETRY_DELAY, KERNEL_CONNECT_TIMEOUT, MAX_CREATE_ATTEMPTS, + OPTIONAL_PROMPT_TIMEOUT, QUICK_PICK_TIMEOUT } from './constants'; import { dismissAllNotifications, waitForNotification } from './notifications'; @@ -58,11 +59,19 @@ export async function createEnvironment(name: string): Promise { 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(); + // On an existing name the create command short-circuits after the name prompt with an + // "already exists" notification and opens no further inputs, so only drive the optional + // prompts when the packages box actually appears. This keeps the documented idempotent + // retry path working: a leftover environment is reused rather than failing the test on a + // timed-out InputBox that never opens. + const packagesBox = await tryOpenInputBox(OPTIONAL_PROMPT_TIMEOUT); + if (packagesBox) { + // Packages (optional) — leave empty. + await packagesBox.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. From de6a2d2f4ce6408db25040aa1f3b019583aff2a0 Mon Sep 17 00:00:00 2001 From: Tomas Kislan Date: Mon, 29 Jun 2026 09:53:22 +0200 Subject: [PATCH 12/15] Update .github/workflows/e2e.yml Co-authored-by: James Hobbs <15235276+jamesbhobbs@users.noreply.github.com> --- .github/workflows/e2e.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index ceb6a08b3e..55475a6fda 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -7,7 +7,6 @@ on: push: branches: [main] pull_request: - branches: [main] workflow_dispatch: permissions: From d8c64e8c3275c38cbb0d871965d3be221e3a43ed Mon Sep 17 00:00:00 2001 From: tomas Date: Tue, 30 Jun 2026 07:53:06 +0000 Subject: [PATCH 13/15] test(e2e): log caught errors instead of swallowing them Add shared logging helpers for best-effort E2E paths and use them in all catch handlers across the suite and helpers so transient UI failures remain visible in test output. --- test/e2e/helpers/deepnoteEnvironment.ts | 3 ++- test/e2e/helpers/index.ts | 1 + test/e2e/helpers/logging.ts | 28 +++++++++++++++++++++++++ test/e2e/helpers/notebook.ts | 22 ++++++++++++------- test/e2e/helpers/notifications.ts | 16 ++++++++++---- test/e2e/helpers/quickInput.ts | 10 ++++++--- test/e2e/helpers/workspace.ts | 14 ++++++++----- test/e2e/suite/helloWorld.e2e.test.ts | 5 +++-- 8 files changed, 77 insertions(+), 22 deletions(-) create mode 100644 test/e2e/helpers/logging.ts diff --git a/test/e2e/helpers/deepnoteEnvironment.ts b/test/e2e/helpers/deepnoteEnvironment.ts index 5b8e5863ac..45dd29e1ec 100644 --- a/test/e2e/helpers/deepnoteEnvironment.ts +++ b/test/e2e/helpers/deepnoteEnvironment.ts @@ -8,6 +8,7 @@ import { OPTIONAL_PROMPT_TIMEOUT, QUICK_PICK_TIMEOUT } from './constants'; +import { catchAndLog } from './logging'; import { dismissAllNotifications, waitForNotification } from './notifications'; import { tryOpenInputBox } from './quickInput'; @@ -46,7 +47,7 @@ export async function createEnvironment(name: string): Promise { 'no Python interpreters were listed' ); } catch (error) { - await interpreterPick.cancel().catch(() => undefined); + await interpreterPick.cancel().catch(catchAndLog('cancel interpreter quick pick', undefined)); await dismissAllNotifications(); await driver.sleep(INTERPRETER_RETRY_DELAY); lastError = error; diff --git a/test/e2e/helpers/index.ts b/test/e2e/helpers/index.ts index c33f734de7..3c06681572 100644 --- a/test/e2e/helpers/index.ts +++ b/test/e2e/helpers/index.ts @@ -2,6 +2,7 @@ export * from './constants'; export * from './deepnoteEnvironment'; export * from './fixtures'; +export * from './logging'; export * from './notebook'; export * from './notifications'; export * from './quickInput'; diff --git a/test/e2e/helpers/logging.ts b/test/e2e/helpers/logging.ts new file mode 100644 index 0000000000..e75eec2de9 --- /dev/null +++ b/test/e2e/helpers/logging.ts @@ -0,0 +1,28 @@ +function formatCaughtError(error: unknown): string { + if (error instanceof Error) { + return error.stack ?? error.message; + } + + return String(error); +} + +/** Logs a caught error from best-effort E2E helper paths that intentionally continue. */ +export function logCaughtError(context: string, error: unknown, expected = false): void { + const detail = formatCaughtError(error); + const prefix = `[deepnote-e2e] ${context}${expected ? ' (expected)' : ''}:`; + + if (expected) { + console.debug(prefix, detail); + } else { + console.warn(prefix, detail); + } +} + +/** Returns a `.catch()` handler that logs and yields `fallback`. */ +export function catchAndLog(context: string, fallback: T, expected = false): (error: unknown) => T { + return (error: unknown) => { + logCaughtError(context, error, expected); + + return fallback; + }; +} diff --git a/test/e2e/helpers/notebook.ts b/test/e2e/helpers/notebook.ts index 1782974f68..a8d5c5d3b9 100644 --- a/test/e2e/helpers/notebook.ts +++ b/test/e2e/helpers/notebook.ts @@ -1,6 +1,7 @@ import { By, EditorView, VSBrowser, WebView } from 'vscode-extension-tester'; import { OUTPUT_POLL_INTERVAL, OUTPUT_SELECTOR, RUN_ALL_REISSUE_INTERVAL, WORKBENCH_TIMEOUT } from './constants'; +import { catchAndLog, logCaughtError } from './logging'; /** * Focuses the given notebook editor and clicks its toolbar "Run All" button. The command-palette @@ -35,7 +36,9 @@ export async function clickRunAll(notebookFileName: string): Promise { */ export async function readRenderedOutput(): Promise { const webView = new WebView(); - const outputFrame = await webView.getViewToSwitchTo().catch(() => undefined); + const outputFrame = await webView + .getViewToSwitchTo() + .catch(catchAndLog('locate notebook output webview', undefined)); if (!outputFrame) { return ''; } @@ -44,19 +47,24 @@ export async function readRenderedOutput(): Promise { 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(() => ''))); + const texts = await Promise.all( + elements.map((element) => element.getText().catch(catchAndLog('read output element text', ''))) + ); 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() : ''; + const body = await webView + .findWebElement(By.css('body')) + .catch(catchAndLog('read output frame body', undefined)); + text = body ? (await body.getText().catch(catchAndLog('read output frame body text', ''))).trim() : ''; } - } catch { + } catch (error) { // Frame went stale or output not painted yet — treat as no output this tick. + logCaughtError('read rendered notebook output', error); } finally { - await webView.switchBack().catch(() => undefined); + await webView.switchBack().catch(catchAndLog('switch back from notebook output webview', undefined)); } return text; @@ -75,7 +83,7 @@ export async function runAndAwaitOutput(notebookFileName: string, expected: stri while (Date.now() < deadline) { if (Date.now() - lastRunAt > RUN_ALL_REISSUE_INTERVAL) { - await clickRunAll(notebookFileName).catch(() => undefined); + await clickRunAll(notebookFileName).catch(catchAndLog('click notebook Run All', undefined)); lastRunAt = Date.now(); } diff --git a/test/e2e/helpers/notifications.ts b/test/e2e/helpers/notifications.ts index 4d9a0f1c1f..6c4def054d 100644 --- a/test/e2e/helpers/notifications.ts +++ b/test/e2e/helpers/notifications.ts @@ -1,10 +1,14 @@ import { Notification, VSBrowser, Workbench } from 'vscode-extension-tester'; +import { catchAndLog, logCaughtError } from './logging'; + /** Dismisses every currently-visible toast notification (best effort). */ export async function dismissAllNotifications(): Promise { - const notifications = await new Workbench().getNotifications().catch(() => [] as Notification[]); + const notifications = await new Workbench() + .getNotifications() + .catch(catchAndLog('get notifications', [] as Notification[])); for (const notification of notifications) { - await notification.dismiss().catch(() => undefined); + await notification.dismiss().catch(catchAndLog('dismiss notification', undefined)); } } @@ -23,9 +27,11 @@ export async function waitForNotification( try { return (await driver.wait( async () => { - const notifications = await new Workbench().getNotifications().catch(() => [] as Notification[]); + const notifications = await new Workbench() + .getNotifications() + .catch(catchAndLog('get notifications', [] as Notification[])); for (const notification of notifications) { - const message = await notification.getMessage().catch(() => ''); + const message = await notification.getMessage().catch(catchAndLog('read notification message', '')); if (pattern.test(message)) { return notification; } @@ -41,6 +47,8 @@ export async function waitForNotification( throw error; } + logCaughtError(`wait for optional notification matching ${pattern}`, error); + return undefined; } } diff --git a/test/e2e/helpers/quickInput.ts b/test/e2e/helpers/quickInput.ts index 02a2911be9..2f9f7ec6f2 100644 --- a/test/e2e/helpers/quickInput.ts +++ b/test/e2e/helpers/quickInput.ts @@ -1,5 +1,7 @@ import { By, InputBox, VSBrowser } from 'vscode-extension-tester'; +import { catchAndLog, logCaughtError } from './logging'; + /** * 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 @@ -8,7 +10,9 @@ import { By, InputBox, VSBrowser } from 'vscode-extension-tester'; export async function tryOpenInputBox(timeout: number): Promise { try { return await InputBox.create(timeout); - } catch { + } catch (error) { + logCaughtError('open input box', error); + return undefined; } } @@ -22,10 +26,10 @@ 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(() => []); + .catch(catchAndLog('find folder dialog buttons', [])); for (const button of buttons) { - const text = (await button.getText().catch(() => '')).trim(); + const text = (await button.getText().catch(catchAndLog('read folder dialog button text', ''))).trim(); if (text === 'OK') { await button.click(); diff --git a/test/e2e/helpers/workspace.ts b/test/e2e/helpers/workspace.ts index 9f4b22dce7..ede8aacf5c 100644 --- a/test/e2e/helpers/workspace.ts +++ b/test/e2e/helpers/workspace.ts @@ -1,6 +1,7 @@ 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 { catchAndLog, logCaughtError } from './logging'; import { clickDialogOkButton } from './quickInput'; /** @@ -51,12 +52,12 @@ export async function openFolderViaDialog(folder: string): Promise { QUICK_PICK_TIMEOUT, 'dialog did not resolve path' ) - .catch(() => undefined); + .catch(catchAndLog('wait for folder dialog path listing', undefined)); await driver.sleep(DIALOG_RESOLVE_DELAY); const accepted = await clickDialogOkButton(); if (!accepted) { - await new InputBox().cancel().catch(() => undefined); + await new InputBox().cancel().catch(catchAndLog('cancel folder dialog', undefined)); continue; } @@ -66,18 +67,21 @@ export async function openFolderViaDialog(folder: string): Promise { await previousWorkbench.getTagName(); return false; - } catch { + } catch (error) { + // Stale element reference means the workbench reloaded. + logCaughtError('detect workbench reload via stale element', error, true); + return true; } }, FOLDER_RELOAD_TIMEOUT) .then(() => true) - .catch(() => false); + .catch(catchAndLog('wait for workbench reload', false)); if (reloaded) { return; } // The folder did not open this time; dismiss any lingering dialog and retry. - await new InputBox().cancel().catch(() => undefined); + await new InputBox().cancel().catch(catchAndLog('cancel lingering folder dialog', 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 a4550d199d..c68d6078fc 100644 --- a/test/e2e/suite/helloWorld.e2e.test.ts +++ b/test/e2e/suite/helloWorld.e2e.test.ts @@ -24,6 +24,7 @@ import { EditorView, VSBrowser, WebView } from 'vscode-extension-tester'; import { OUTPUT_TIMEOUT, WORKBENCH_TIMEOUT, + catchAndLog, copyFixtureToTempDir, createEnvironment, openFolderViaDialog, @@ -73,8 +74,8 @@ describe('Deepnote E2E — run "hello world"', function () { 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); + await new WebView().switchBack().catch(catchAndLog('switch back from webview during cleanup', undefined)); + await new EditorView().closeAllEditors().catch(catchAndLog('close all editors during cleanup', undefined)); }); it('creates an environment, connects the kernel, runs the cell and renders output', async function () { From 0163085e606393f565cfbb8ad2a5b67a3f46c5ee Mon Sep 17 00:00:00 2001 From: tomas Date: Tue, 30 Jun 2026 08:39:00 +0000 Subject: [PATCH 14/15] test(e2e): inline caught-error logging in catch blocks Remove the logCaughtError/catchAndLog helpers and log directly where errors are caught. --- test/e2e/helpers/deepnoteEnvironment.ts | 5 +-- test/e2e/helpers/index.ts | 1 - test/e2e/helpers/logging.ts | 28 --------------- test/e2e/helpers/notebook.ts | 45 ++++++++++++++++++------- test/e2e/helpers/notifications.ts | 30 +++++++++++------ test/e2e/helpers/quickInput.ts | 18 +++++++--- test/e2e/helpers/workspace.ts | 21 ++++++++---- test/e2e/suite/helloWorld.e2e.test.ts | 9 +++-- 8 files changed, 89 insertions(+), 68 deletions(-) delete mode 100644 test/e2e/helpers/logging.ts diff --git a/test/e2e/helpers/deepnoteEnvironment.ts b/test/e2e/helpers/deepnoteEnvironment.ts index 45dd29e1ec..275235e035 100644 --- a/test/e2e/helpers/deepnoteEnvironment.ts +++ b/test/e2e/helpers/deepnoteEnvironment.ts @@ -8,7 +8,6 @@ import { OPTIONAL_PROMPT_TIMEOUT, QUICK_PICK_TIMEOUT } from './constants'; -import { catchAndLog } from './logging'; import { dismissAllNotifications, waitForNotification } from './notifications'; import { tryOpenInputBox } from './quickInput'; @@ -47,7 +46,9 @@ export async function createEnvironment(name: string): Promise { 'no Python interpreters were listed' ); } catch (error) { - await interpreterPick.cancel().catch(catchAndLog('cancel interpreter quick pick', undefined)); + await interpreterPick.cancel().catch((cancelError) => { + console.warn('[deepnote-e2e] cancel interpreter quick pick:', cancelError); + }); await dismissAllNotifications(); await driver.sleep(INTERPRETER_RETRY_DELAY); lastError = error; diff --git a/test/e2e/helpers/index.ts b/test/e2e/helpers/index.ts index 3c06681572..c33f734de7 100644 --- a/test/e2e/helpers/index.ts +++ b/test/e2e/helpers/index.ts @@ -2,7 +2,6 @@ export * from './constants'; export * from './deepnoteEnvironment'; export * from './fixtures'; -export * from './logging'; export * from './notebook'; export * from './notifications'; export * from './quickInput'; diff --git a/test/e2e/helpers/logging.ts b/test/e2e/helpers/logging.ts deleted file mode 100644 index e75eec2de9..0000000000 --- a/test/e2e/helpers/logging.ts +++ /dev/null @@ -1,28 +0,0 @@ -function formatCaughtError(error: unknown): string { - if (error instanceof Error) { - return error.stack ?? error.message; - } - - return String(error); -} - -/** Logs a caught error from best-effort E2E helper paths that intentionally continue. */ -export function logCaughtError(context: string, error: unknown, expected = false): void { - const detail = formatCaughtError(error); - const prefix = `[deepnote-e2e] ${context}${expected ? ' (expected)' : ''}:`; - - if (expected) { - console.debug(prefix, detail); - } else { - console.warn(prefix, detail); - } -} - -/** Returns a `.catch()` handler that logs and yields `fallback`. */ -export function catchAndLog(context: string, fallback: T, expected = false): (error: unknown) => T { - return (error: unknown) => { - logCaughtError(context, error, expected); - - return fallback; - }; -} diff --git a/test/e2e/helpers/notebook.ts b/test/e2e/helpers/notebook.ts index a8d5c5d3b9..2a9e148df6 100644 --- a/test/e2e/helpers/notebook.ts +++ b/test/e2e/helpers/notebook.ts @@ -1,7 +1,6 @@ import { By, EditorView, VSBrowser, WebView } from 'vscode-extension-tester'; import { OUTPUT_POLL_INTERVAL, OUTPUT_SELECTOR, RUN_ALL_REISSUE_INTERVAL, WORKBENCH_TIMEOUT } from './constants'; -import { catchAndLog, logCaughtError } from './logging'; /** * Focuses the given notebook editor and clicks its toolbar "Run All" button. The command-palette @@ -36,9 +35,11 @@ export async function clickRunAll(notebookFileName: string): Promise { */ export async function readRenderedOutput(): Promise { const webView = new WebView(); - const outputFrame = await webView - .getViewToSwitchTo() - .catch(catchAndLog('locate notebook output webview', undefined)); + const outputFrame = await webView.getViewToSwitchTo().catch((error) => { + console.warn('[deepnote-e2e] locate notebook output webview:', error); + + return undefined; + }); if (!outputFrame) { return ''; } @@ -48,23 +49,41 @@ export async function readRenderedOutput(): Promise { 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(catchAndLog('read output element text', ''))) + elements.map((element) => + element.getText().catch((error) => { + console.warn('[deepnote-e2e] read output element text:', error); + + return ''; + }) + ) ); 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(catchAndLog('read output frame body', undefined)); - text = body ? (await body.getText().catch(catchAndLog('read output frame body text', ''))).trim() : ''; + const body = await webView.findWebElement(By.css('body')).catch((error) => { + console.warn('[deepnote-e2e] read output frame body:', error); + + return undefined; + }); + text = body + ? ( + await body.getText().catch((error) => { + console.warn('[deepnote-e2e] read output frame body text:', error); + + return ''; + }) + ).trim() + : ''; } } catch (error) { // Frame went stale or output not painted yet — treat as no output this tick. - logCaughtError('read rendered notebook output', error); + console.warn('[deepnote-e2e] read rendered notebook output:', error); } finally { - await webView.switchBack().catch(catchAndLog('switch back from notebook output webview', undefined)); + await webView.switchBack().catch((error) => { + console.warn('[deepnote-e2e] switch back from notebook output webview:', error); + }); } return text; @@ -83,7 +102,9 @@ export async function runAndAwaitOutput(notebookFileName: string, expected: stri while (Date.now() < deadline) { if (Date.now() - lastRunAt > RUN_ALL_REISSUE_INTERVAL) { - await clickRunAll(notebookFileName).catch(catchAndLog('click notebook Run All', undefined)); + await clickRunAll(notebookFileName).catch((error) => { + console.warn('[deepnote-e2e] click notebook Run All:', error); + }); lastRunAt = Date.now(); } diff --git a/test/e2e/helpers/notifications.ts b/test/e2e/helpers/notifications.ts index 6c4def054d..7f5ccadded 100644 --- a/test/e2e/helpers/notifications.ts +++ b/test/e2e/helpers/notifications.ts @@ -1,14 +1,16 @@ import { Notification, VSBrowser, Workbench } from 'vscode-extension-tester'; -import { catchAndLog, logCaughtError } from './logging'; - /** Dismisses every currently-visible toast notification (best effort). */ export async function dismissAllNotifications(): Promise { - const notifications = await new Workbench() - .getNotifications() - .catch(catchAndLog('get notifications', [] as Notification[])); + const notifications = await new Workbench().getNotifications().catch((error) => { + console.warn('[deepnote-e2e] get notifications:', error); + + return [] as Notification[]; + }); for (const notification of notifications) { - await notification.dismiss().catch(catchAndLog('dismiss notification', undefined)); + await notification.dismiss().catch((error) => { + console.warn('[deepnote-e2e] dismiss notification:', error); + }); } } @@ -27,11 +29,17 @@ export async function waitForNotification( try { return (await driver.wait( async () => { - const notifications = await new Workbench() - .getNotifications() - .catch(catchAndLog('get notifications', [] as Notification[])); + const notifications = await new Workbench().getNotifications().catch((error) => { + console.warn('[deepnote-e2e] get notifications:', error); + + return [] as Notification[]; + }); for (const notification of notifications) { - const message = await notification.getMessage().catch(catchAndLog('read notification message', '')); + const message = await notification.getMessage().catch((error) => { + console.warn('[deepnote-e2e] read notification message:', error); + + return ''; + }); if (pattern.test(message)) { return notification; } @@ -47,7 +55,7 @@ export async function waitForNotification( throw error; } - logCaughtError(`wait for optional notification matching ${pattern}`, error); + console.warn(`[deepnote-e2e] wait for optional notification matching ${pattern}:`, error); return undefined; } diff --git a/test/e2e/helpers/quickInput.ts b/test/e2e/helpers/quickInput.ts index 2f9f7ec6f2..b787072bf9 100644 --- a/test/e2e/helpers/quickInput.ts +++ b/test/e2e/helpers/quickInput.ts @@ -1,7 +1,5 @@ import { By, InputBox, VSBrowser } from 'vscode-extension-tester'; -import { catchAndLog, logCaughtError } from './logging'; - /** * 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 @@ -11,7 +9,7 @@ export async function tryOpenInputBox(timeout: number): Promise { const driver = VSBrowser.instance.driver; const buttons = await driver .findElements(By.css('.quick-input-widget .monaco-button.monaco-text-button')) - .catch(catchAndLog('find folder dialog buttons', [])); + .catch((error) => { + console.warn('[deepnote-e2e] find folder dialog buttons:', error); + + return []; + }); for (const button of buttons) { - const text = (await button.getText().catch(catchAndLog('read folder dialog button text', ''))).trim(); + const text = ( + await button.getText().catch((error) => { + console.warn('[deepnote-e2e] read folder dialog button text:', error); + + return ''; + }) + ).trim(); if (text === 'OK') { await button.click(); diff --git a/test/e2e/helpers/workspace.ts b/test/e2e/helpers/workspace.ts index ede8aacf5c..4e841c39cc 100644 --- a/test/e2e/helpers/workspace.ts +++ b/test/e2e/helpers/workspace.ts @@ -1,7 +1,6 @@ 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 { catchAndLog, logCaughtError } from './logging'; import { clickDialogOkButton } from './quickInput'; /** @@ -52,12 +51,16 @@ export async function openFolderViaDialog(folder: string): Promise { QUICK_PICK_TIMEOUT, 'dialog did not resolve path' ) - .catch(catchAndLog('wait for folder dialog path listing', undefined)); + .catch((error) => { + console.warn('[deepnote-e2e] wait for folder dialog path listing:', error); + }); await driver.sleep(DIALOG_RESOLVE_DELAY); const accepted = await clickDialogOkButton(); if (!accepted) { - await new InputBox().cancel().catch(catchAndLog('cancel folder dialog', undefined)); + await new InputBox().cancel().catch((error) => { + console.warn('[deepnote-e2e] cancel folder dialog:', error); + }); continue; } @@ -69,19 +72,25 @@ export async function openFolderViaDialog(folder: string): Promise { return false; } catch (error) { // Stale element reference means the workbench reloaded. - logCaughtError('detect workbench reload via stale element', error, true); + console.debug('[deepnote-e2e] detect workbench reload via stale element (expected):', error); return true; } }, FOLDER_RELOAD_TIMEOUT) .then(() => true) - .catch(catchAndLog('wait for workbench reload', false)); + .catch((error) => { + console.warn('[deepnote-e2e] wait for workbench reload:', error); + + return false; + }); if (reloaded) { return; } // The folder did not open this time; dismiss any lingering dialog and retry. - await new InputBox().cancel().catch(catchAndLog('cancel lingering folder dialog', undefined)); + await new InputBox().cancel().catch((error) => { + console.warn('[deepnote-e2e] cancel lingering folder dialog:', error); + }); } 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 c68d6078fc..4bcfd2303a 100644 --- a/test/e2e/suite/helloWorld.e2e.test.ts +++ b/test/e2e/suite/helloWorld.e2e.test.ts @@ -24,7 +24,6 @@ import { EditorView, VSBrowser, WebView } from 'vscode-extension-tester'; import { OUTPUT_TIMEOUT, WORKBENCH_TIMEOUT, - catchAndLog, copyFixtureToTempDir, createEnvironment, openFolderViaDialog, @@ -74,8 +73,12 @@ describe('Deepnote E2E — run "hello world"', function () { after(async function () { // Defensive cleanup: never leave the driver stuck inside a webview frame, and close tabs. - await new WebView().switchBack().catch(catchAndLog('switch back from webview during cleanup', undefined)); - await new EditorView().closeAllEditors().catch(catchAndLog('close all editors during cleanup', undefined)); + await new WebView().switchBack().catch((error) => { + console.warn('[deepnote-e2e] switch back from webview during cleanup:', error); + }); + await new EditorView().closeAllEditors().catch((error) => { + console.warn('[deepnote-e2e] close all editors during cleanup:', error); + }); }); it('creates an environment, connects the kernel, runs the cell and renders output', async function () { From ffc2d64400147d5eb0d2bb4c84c1b7e5d8a81500 Mon Sep 17 00:00:00 2001 From: tomas Date: Tue, 30 Jun 2026 19:29:17 +0000 Subject: [PATCH 15/15] fix(e2e): address CodeRabbit and Dino review feedback Resolve the review comments from CodeRabbit and Dino on PR #430. Major: - ci: set persist-credentials: false on actions/checkout in e2e.yml so the GITHUB_TOKEN is not left in git config for PR-controlled build steps to read. - deps: declare @vscode/vsce as a devDependency and the lightningcss / @tailwindcss/oxide linux-x64 binaries as optionalDependencies, then drop the unpinned `npm install` fallbacks and the global vsce install from both e2e.yml and cd.yml. The native binaries are os/cpu constrained, so optionalDependencies (installed on linux, skipped elsewhere) keeps cross-platform installs working while removing the lockfile-bypassing fallback. - deepnote: in ensureControllerSelectedForNotebook, return early with a warning when findNotebookEditor() yields no editor instead of passing a NotebookDocument to notebook.selectKernel, matching selectPlaceholderController. Passing the document can leave the controller unbound and silently drop the first execution. Test guard (Dino): - e2e: replace the re-click-every-5s runAndAwaitOutput loop with a single-run runOnceAndAwaitOutput so a dropped first execution fails the test instead of being masked by a later re-run. Make clickRunAll retry the find+click atomically to survive the StaleElementReferenceError toolbar race without re-running the notebook. Drop the now-unused RUN_ALL_REISSUE_INTERVAL/OUTPUT_TIMEOUT constants and add FIRST_RUN_OUTPUT_TIMEOUT. Nitpicks: - e2e: clean up the throwaway fixture temp dir in the suite's after() hook. - e2e: extract INTERPRETER_PROMPT_TIMEOUT, OUTPUT_FRAME_SWITCH_TIMEOUT and SUITE_TIMEOUT named constants (consistent _000 formatting). Verified: full project + e2e type-check, formatter, and the ExTester e2e suite running under Xvfb (1 passing). Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_019VuoSZXF8YHsN81Lra45JW --- .github/workflows/cd.yml | 10 +-- .github/workflows/e2e.yml | 12 +--- package-lock.json | 9 ++- package.json | 5 +- .../deepnoteKernelAutoSelector.node.ts | 10 ++- test/e2e/helpers/constants.ts | 24 +++++-- test/e2e/helpers/deepnoteEnvironment.ts | 3 +- test/e2e/helpers/fixtures.ts | 16 +++-- test/e2e/helpers/notebook.ts | 66 +++++++++++++------ test/e2e/suite/helloWorld.e2e.test.ts | 22 +++++-- 10 files changed, 112 insertions(+), 65 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index d29a6966d8..473d733d08 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -30,15 +30,7 @@ jobs: node-version-file: '.nvmrc' - name: Install dependencies - run: | - npm ci --no-audit - # Verify Tailwind CSS native modules are installed for Linux (npm optional deps bug) - # See: https://github.com/npm/cli/issues/4828 - node -e "try { require('lightningcss'); } catch { process.exit(1); }" 2>/dev/null || npm install lightningcss-linux-x64-gnu - node -e "try { require('@tailwindcss/oxide'); } catch { process.exit(1); }" 2>/dev/null || npm install @tailwindcss/oxide-linux-x64-gnu - - - name: Install vsce - run: npm install -g @vscode/vsce + run: npm ci --no-audit - name: Extract version from package.json id: package-version diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 55475a6fda..d8467dc7fb 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -29,6 +29,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false - name: Setup Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 @@ -42,15 +44,7 @@ jobs: python-version: '3.12' - name: Install dependencies - run: | - npm ci --prefer-offline --no-audit - # Verify Tailwind CSS native modules are installed for Linux (npm optional deps bug) - # See: https://github.com/npm/cli/issues/4828 - node -e "try { require('lightningcss'); } catch { process.exit(1); }" 2>/dev/null || npm install lightningcss-linux-x64-gnu - node -e "try { require('@tailwindcss/oxide'); } catch { process.exit(1); }" 2>/dev/null || npm install @tailwindcss/oxide-linux-x64-gnu - - - name: Install vsce - run: npm install -g @vscode/vsce + run: npm ci --prefer-offline --no-audit - name: Package extension run: npm run package diff --git a/package-lock.json b/package-lock.json index 0c20bf31e4..940a3c7924 100644 --- a/package-lock.json +++ b/package-lock.json @@ -162,6 +162,7 @@ "@vscode/test-cli": "^0.0.8", "@vscode/test-electron": "^2.3.9", "@vscode/test-web": "^0.0.71", + "@vscode/vsce": "3.9.2", "@vscode/zeromq": "^0.2.3", "acorn": "^8.9.0", "autoprefixer": "^10.4.21", @@ -246,7 +247,9 @@ "vscode": "^1.95.0" }, "optionalDependencies": { - "fsevents": "^2.3.2" + "@tailwindcss/oxide-linux-x64-gnu": "4.1.14", + "fsevents": "^2.3.2", + "lightningcss-linux-x64-gnu": "1.30.1" } }, "build/eslint-rules": { @@ -7493,7 +7496,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -23527,7 +23529,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -40537,7 +40538,6 @@ "version": "4.1.14", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.14.tgz", "integrity": "sha512-02c6JhLPJj10L2caH4U0zF8Hji4dOeahmuMl23stk0MU1wfd1OraE7rOloidSF8W5JTHkFdVo/O7uRUJJnUAJg==", - "dev": true, "optional": true }, "@tailwindcss/oxide-linux-x64-musl": { @@ -51999,7 +51999,6 @@ "version": "1.30.1", "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", - "dev": true, "optional": true }, "lightningcss-linux-x64-musl": { diff --git a/package.json b/package.json index fc1c6d8de7..9a631dea25 100644 --- a/package.json +++ b/package.json @@ -2840,6 +2840,7 @@ "@vscode/test-cli": "^0.0.8", "@vscode/test-electron": "^2.3.9", "@vscode/test-web": "^0.0.71", + "@vscode/vsce": "3.9.2", "@vscode/zeromq": "^0.2.3", "acorn": "^8.9.0", "autoprefixer": "^10.4.21", @@ -2927,7 +2928,9 @@ ] }, "optionalDependencies": { - "fsevents": "^2.3.2" + "@tailwindcss/oxide-linux-x64-gnu": "4.1.14", + "fsevents": "^2.3.2", + "lightningcss-linux-x64-gnu": "1.30.1" }, "overrides": { "braces": "3.0.3", diff --git a/src/notebooks/deepnote/deepnoteKernelAutoSelector.node.ts b/src/notebooks/deepnote/deepnoteKernelAutoSelector.node.ts index a3b1fa799e..bd0a3797af 100644 --- a/src/notebooks/deepnote/deepnoteKernelAutoSelector.node.ts +++ b/src/notebooks/deepnote/deepnoteKernelAutoSelector.node.ts @@ -743,10 +743,16 @@ export class DeepnoteKernelAutoSelector implements IDeepnoteKernelAutoSelector, // happens to settle — observed as a multi-minute stall before the first cell runs. const notebookEditor = await this.findNotebookEditor(notebook); + if (!notebookEditor) { + logger.warn( + `Could not find NotebookEditor for ${getDisplayPath(notebook.uri)}, kernel may not be selected` + ); + return; + } + await commands.executeCommand('notebook.selectKernel', { - notebookEditor: notebookEditor ?? notebook, + notebookEditor, id: controller.connection.id, - // id: controller.controller.id, extension: JVSC_EXTENSION_ID }); } diff --git a/test/e2e/helpers/constants.ts b/test/e2e/helpers/constants.ts index 96a0df7d74..3ddd57859c 100644 --- a/test/e2e/helpers/constants.ts +++ b/test/e2e/helpers/constants.ts @@ -5,20 +5,32 @@ 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. VS Code drops the first run request(s) -// while the kernel is still connecting, so we keep nudging it; a short interval makes recovery from -// a dropped run fast (a coarse interval can add a full interval's delay per dropped run — observed -// adding ~100s with a 25s value). -export const RUN_ALL_REISSUE_INTERVAL = 5_000; +// Mocha per-test timeout applied to the whole suite (overrides the .mocharc default). Stays just +// under the 25 min .mocharc.js default; the slowest single step is the first kernel start (venv + +// Deepnote toolkit provisioning). +export const SUITE_TIMEOUT = 1_320_000; // 22 min + +// A single "Run All" against an already-selected kernel must render output within this window. It +// sits well above a healthy first run (the kernel is bound before the click — see +// selectEnvironmentForNotebook) and below the multi-minute stall a dropped first run would cause, +// so the kernel-binding regression fails here instead of being masked by re-runs. +export const FIRST_RUN_OUTPUT_TIMEOUT = 120_000; // How often to poll the output webview for the expected text. export const OUTPUT_POLL_INTERVAL = 1_500; +// How long to wait for the notebook output iframe (`#active-frame`) to become switchable. +export const OUTPUT_FRAME_SWITCH_TIMEOUT = 5_000; + export const INTERPRETER_RETRY_DELAY = 5_000; export const MAX_CREATE_ATTEMPTS = 6; +// How long to wait for the interpreter quick pick to open after issuing the create-environment +// command. When no interpreter has been discovered yet the command shows a "No Python interpreters +// found" notification and returns instead, so this wait elapses and the attempt is retried. +export const INTERPRETER_PROMPT_TIMEOUT = 5_000; + // How long to wait for an optional input box (packages/description) to appear after confirming the // environment name. When the name already exists the create command short-circuits with an "already // exists" notification and opens no further inputs, so this wait elapses and the prompts are skipped. diff --git a/test/e2e/helpers/deepnoteEnvironment.ts b/test/e2e/helpers/deepnoteEnvironment.ts index 275235e035..a4f996ce49 100644 --- a/test/e2e/helpers/deepnoteEnvironment.ts +++ b/test/e2e/helpers/deepnoteEnvironment.ts @@ -2,6 +2,7 @@ import { EditorView, InputBox, VSBrowser, Workbench } from 'vscode-extension-tes import { ENV_CREATED_TIMEOUT, + INTERPRETER_PROMPT_TIMEOUT, INTERPRETER_RETRY_DELAY, KERNEL_CONNECT_TIMEOUT, MAX_CREATE_ATTEMPTS, @@ -31,7 +32,7 @@ export async function createEnvironment(name: string): Promise { // 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); + const interpreterPick = await tryOpenInputBox(INTERPRETER_PROMPT_TIMEOUT); if (!interpreterPick) { await dismissAllNotifications(); await driver.sleep(INTERPRETER_RETRY_DELAY); diff --git a/test/e2e/helpers/fixtures.ts b/test/e2e/helpers/fixtures.ts index eef14b063a..b799b3900d 100644 --- a/test/e2e/helpers/fixtures.ts +++ b/test/e2e/helpers/fixtures.ts @@ -3,16 +3,18 @@ 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; + /** Removes the throwaway temp directory and its contents. Idempotent; safe to call more than once. */ + cleanup: () => void; /** The absolute path to the copied fixture file inside `tempDir`. */ filePath: string; + /** The throwaway temp directory the fixture was copied into (suitable as a workspace folder). */ + tempDir: 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. + * Copies a fixture from `test/e2e/fixtures` into a fresh throwaway temp directory and returns the + * paths plus a `cleanup` callback that removes the dir. 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); @@ -20,5 +22,7 @@ export function copyFixtureToTempDir(fixtureName: string): FixtureCopy { const filePath = path.join(tempDir, fixtureName); fs.copyFileSync(source, filePath); - return { tempDir, filePath }; + const cleanup = () => fs.rmSync(tempDir, { recursive: true, force: true }); + + return { cleanup, filePath, tempDir }; } diff --git a/test/e2e/helpers/notebook.ts b/test/e2e/helpers/notebook.ts index 2a9e148df6..6fc561a7b3 100644 --- a/test/e2e/helpers/notebook.ts +++ b/test/e2e/helpers/notebook.ts @@ -1,6 +1,7 @@ import { By, EditorView, VSBrowser, WebView } from 'vscode-extension-tester'; -import { OUTPUT_POLL_INTERVAL, OUTPUT_SELECTOR, RUN_ALL_REISSUE_INTERVAL, WORKBENCH_TIMEOUT } from './constants'; +import { OUTPUT_FRAME_SWITCH_TIMEOUT, OUTPUT_POLL_INTERVAL, OUTPUT_SELECTOR, WORKBENCH_TIMEOUT } from './constants'; +import { dismissAllNotifications } from './notifications'; /** * Focuses the given notebook editor and clicks its toolbar "Run All" button. The command-palette @@ -13,16 +14,31 @@ export async function clickRunAll(notebookFileName: string): Promise { await new EditorView().openEditor(notebookFileName); - const runAllButton = await driver.wait( + // Locate AND click inside the same wait loop. The notebook toolbar can re-render between finding + // the button and clicking it (the editor re-focuses, kernel status / notifications change), which + // would otherwise surface as a StaleElementReferenceError. Re-finding and clicking on the next + // tick is still a SINGLE "Run All" — the run is only issued once the click actually lands, so + // this does not re-run a notebook whose first execution was accepted. + await driver.wait( async () => { - const [button] = await driver.findElements(By.css('a.action-label[aria-label="Run All"]')); + try { + const [button] = await driver.findElements(By.css('a.action-label[aria-label="Run All"]')); + if (!button) { + return false; + } - return button; + await button.click(); + + return true; + } catch (error) { + console.warn('[deepnote-e2e] locate/click notebook Run All (retrying):', error); + + return false; + } }, WORKBENCH_TIMEOUT, - 'notebook "Run All" button did not appear' + 'notebook "Run All" button did not appear or could not be clicked' ); - await runAllButton.click(); } /** @@ -46,7 +62,7 @@ export async function readRenderedOutput(): Promise { let text = ''; try { - await webView.switchToFrame(5_000); + await webView.switchToFrame(OUTPUT_FRAME_SWITCH_TIMEOUT); const elements = await webView.findWebElements(By.css(OUTPUT_SELECTOR)); const texts = await Promise.all( elements.map((element) => @@ -90,24 +106,31 @@ export async function readRenderedOutput(): Promise { } /** - * 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). + * Issues a SINGLE "Run All" after the kernel has been selected and polls the notebook output webview + * until the expected text renders. It deliberately does NOT re-issue "Run All" when output is + * missing: the kernel is already bound before we get here (`selectEnvironmentForNotebook` waits for + * the post-binding "switched successfully" toast), so a first run that renders nothing means the + * execution request was dropped — exactly the kernel-binding regression this suite must catch. + * Re-running until output eventually appeared would mask that bug. */ -export async function runAndAwaitOutput(notebookFileName: string, expected: string, timeout: number): Promise { +export async function runOnceAndAwaitOutput( + notebookFileName: string, + expected: string, + timeout: number +): Promise { const driver = VSBrowser.instance.driver; + + // Clear the "switched successfully" toast so it cannot intercept the single toolbar click. + await dismissAllNotifications().catch((error) => { + console.warn('[deepnote-e2e] dismiss notifications before Run All:', error); + }); + + await clickRunAll(notebookFileName); + 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((error) => { - console.warn('[deepnote-e2e] click notebook Run All:', error); - }); - lastRunAt = Date.now(); - } - lastText = await readRenderedOutput(); if (lastText.includes(expected)) { return lastText; @@ -117,7 +140,8 @@ export async function runAndAwaitOutput(notebookFileName: string, expected: stri } throw new Error( - `Timed out after ${timeout}ms waiting for rendered output to contain "${expected}". ` + - `Last observed output: ${JSON.stringify(lastText)}` + `Timed out after ${timeout}ms waiting for the first "Run All" to render output containing ` + + `"${expected}". No re-run was issued, so a dropped first execution (kernel not bound to a ` + + `NotebookEditor) surfaces here. Last observed output: ${JSON.stringify(lastText)}` ); } diff --git a/test/e2e/suite/helloWorld.e2e.test.ts b/test/e2e/suite/helloWorld.e2e.test.ts index 4bcfd2303a..5090937982 100644 --- a/test/e2e/suite/helloWorld.e2e.test.ts +++ b/test/e2e/suite/helloWorld.e2e.test.ts @@ -22,13 +22,14 @@ import { expect } from 'chai'; import { EditorView, VSBrowser, WebView } from 'vscode-extension-tester'; import { - OUTPUT_TIMEOUT, + FIRST_RUN_OUTPUT_TIMEOUT, + SUITE_TIMEOUT, WORKBENCH_TIMEOUT, copyFixtureToTempDir, createEnvironment, openFolderViaDialog, openWorkspaceFile, - runAndAwaitOutput, + runOnceAndAwaitOutput, selectEnvironmentForNotebook } from '../helpers'; @@ -37,16 +38,20 @@ const EXPECTED_OUTPUT = 'hello world'; 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); + this.timeout(SUITE_TIMEOUT); // 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'; + // Captured in `before` and invoked in `after` to remove the throwaway temp dir. + let cleanupTempDir: (() => void) | undefined; + before(async function () { // Work on a throwaway copy so execution-dirtied notebook state never touches the source tree. - const { tempDir } = copyFixtureToTempDir(NOTEBOOK_FILE_NAME); + const { cleanup, tempDir } = copyFixtureToTempDir(NOTEBOOK_FILE_NAME); + cleanupTempDir = cleanup; await VSBrowser.instance.waitForWorkbench(WORKBENCH_TIMEOUT); @@ -79,13 +84,20 @@ describe('Deepnote E2E — run "hello world"', function () { await new EditorView().closeAllEditors().catch((error) => { console.warn('[deepnote-e2e] close all editors during cleanup:', error); }); + + // Remove the throwaway temp dir last so a failure above can't leak it. + try { + cleanupTempDir?.(); + } catch (error) { + console.warn('[deepnote-e2e] remove temp workspace dir during cleanup:', error); + } }); it('creates an environment, connects the kernel, runs the cell and renders output', async function () { await createEnvironment(environmentName); await selectEnvironmentForNotebook(environmentName, NOTEBOOK_FILE_NAME); - const renderedOutput = await runAndAwaitOutput(NOTEBOOK_FILE_NAME, EXPECTED_OUTPUT, OUTPUT_TIMEOUT); + const renderedOutput = await runOnceAndAwaitOutput(NOTEBOOK_FILE_NAME, EXPECTED_OUTPUT, FIRST_RUN_OUTPUT_TIMEOUT); expect(renderedOutput).to.contain(EXPECTED_OUTPUT); }); });