From 48c885d108250136b91c26dc185e69daf91739d3 Mon Sep 17 00:00:00 2001 From: Yassine El Khattabi Date: Thu, 27 Mar 2025 13:39:58 +0000 Subject: [PATCH 01/32] Add prettier --- .prettierrc | 9 + .vscode/launch.json | 54 +- .vscode/settings.json | 14 +- .vscode/tasks.json | 69 +- README.md | 8 +- eslint.config.mjs | 73 +- package-lock.json | 4183 +++++++++++++++++++++-------------------- package.json | 382 ++-- src/cli.ts | 64 +- src/extension.ts | 485 +++-- src/simple.ts | 232 +-- src/spec.ts | 2 +- src/testTree.ts | 161 +- tsconfig.json | 34 +- 14 files changed, 2940 insertions(+), 2830 deletions(-) create mode 100644 .prettierrc diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..a9253e9 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "singleQuote": true, + "useTabs": false, + "printWidth": 80, + "arrowParens": "always", + "trailingComma": "all", + "tabWidth": 2, + "semi": true +} diff --git a/.vscode/launch.json b/.vscode/launch.json index 9bc4c02..84a0bef 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -3,33 +3,29 @@ // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 { - "version": "0.2.0", - "configurations": [ - { - "name": "Run Extension", - "type": "extensionHost", - "request": "launch", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}" - ], - "outFiles": [ - "${workspaceFolder}/out/**/*.js" - ], - "preLaunchTask": "${defaultBuildTask}" - }, - { - "name": "Extension Tests", - "type": "extensionHost", - "request": "launch", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}", - "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" - ], - "outFiles": [ - "${workspaceFolder}/out/**/*.js", - "${workspaceFolder}/dist/**/*.js" - ], - "preLaunchTask": "tasks: watch-tests" - } - ] + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "outFiles": ["${workspaceFolder}/out/**/*.js"], + "preLaunchTask": "${defaultBuildTask}" + }, + { + "name": "Extension Tests", + "type": "extensionHost", + "request": "launch", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}", + "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" + ], + "outFiles": [ + "${workspaceFolder}/out/**/*.js", + "${workspaceFolder}/dist/**/*.js" + ], + "preLaunchTask": "tasks: watch-tests" + } + ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 657212c..e7df4dc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,9 +1,7 @@ { - "search.exclude": { - "out": true - }, - "git.branchProtection": [ - "main" - ], - "files.trimTrailingWhitespace": true -} \ No newline at end of file + "search.exclude": { + "out": true + }, + "git.branchProtection": ["main"], + "files.trimTrailingWhitespace": true +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 0dc1143..b9dd9f7 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,40 +1,37 @@ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format { - "version": "2.0.0", - "tasks": [ - { - "type": "npm", - "script": "watch", - "problemMatcher": "$tsc-watch", - "isBackground": true, - "presentation": { - "reveal": "never", - "group": "watchers" - }, - "group": { - "kind": "build", - "isDefault": true - } - }, - { - "type": "npm", - "script": "watch-tests", - "problemMatcher": "$tsc-watch", - "isBackground": true, - "presentation": { - "reveal": "never", - "group": "watchers" - }, - "group": "build" - }, - { - "label": "tasks: watch-tests", - "dependsOn": [ - "npm: watch", - "npm: watch-tests" - ], - "problemMatcher": [] - } - ] + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "watch", + "problemMatcher": "$tsc-watch", + "isBackground": true, + "presentation": { + "reveal": "never", + "group": "watchers" + }, + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "type": "npm", + "script": "watch-tests", + "problemMatcher": "$tsc-watch", + "isBackground": true, + "presentation": { + "reveal": "never", + "group": "watchers" + }, + "group": "build" + }, + { + "label": "tasks: watch-tests", + "dependsOn": ["npm: watch", "npm: watch-tests"], + "problemMatcher": [] + } + ] } diff --git a/README.md b/README.md index 3fe35cd..e12f5be 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ This GitHub Copilot Extension sample shows: ![demo](./demo.png) Documentation can be found here: + - https://code.visualstudio.com/api/extension-guides/cha - https://code.visualstudio.com/api/extension-guides/language-model @@ -23,9 +24,9 @@ Documentation can be found here: - Run `npm install` in terminal to install dependencies - Run the `Run Extension` target in the Debug View. This will: - - Start a task `npm: watch` to compile the code - - Run the extension in a new VS Code window - - You will see the @cat chat participant show in the GitHub Copilot Chat view + - Start a task `npm: watch` to compile the code + - Run the extension in a new VS Code window + - You will see the @cat chat participant show in the GitHub Copilot Chat view ## About this sample @@ -34,4 +35,5 @@ This sample shows two different ways to build a chat participant in VS Code: See [simple.ts](src/simple.ts) for an example of a simple chat participant that makes requests and responds to user queries. It shows how you can create chat participants with or without the [@vscode/prompt-tsx](https://www.npmjs.com/package/@vscode/prompt-tsx) library. See [toolParticipant.ts](src/toolParticipant.ts) for an example of a chat participant that invokes tools, either dynamically or using the `toolReferences` that are attached to the request. This is a more advanced example that shows how you can use the [@vscode/prompt-tsx](https://www.npmjs.com/package/@vscode/prompt-tsx) library to implement the LLM tool calling flow and tries to implement all the features of the chat API. + # vscode diff --git a/eslint.config.mjs b/eslint.config.mjs index 84abd1b..06d7f73 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,6 +1,6 @@ /** * ESLint configuration for the project. - * + * * See https://eslint.style and https://typescript-eslint.io for additional linting options. */ // @ts-check @@ -9,40 +9,37 @@ import tseslint from 'typescript-eslint'; import stylistic from '@stylistic/eslint-plugin'; export default tseslint.config( - { - ignores: [ - '.vscode-test', - 'out', - ] - }, - { - files: ['**/*.{js,mjs,cjs,ts,jsx,tsx}'], - }, - js.configs.recommended, - ...tseslint.configs.recommended, - ...tseslint.configs.stylistic, - { - plugins: { - '@stylistic': stylistic - }, - rules: { - 'curly': 'warn', - '@stylistic/semi': ['warn', 'always'], - '@typescript-eslint/no-empty-function': 'off', - '@typescript-eslint/array-type': 'off', - '@typescript-eslint/naming-convention': [ - 'warn', - { - 'selector': 'import', - 'format': ['camelCase', 'PascalCase'] - } - ], - '@typescript-eslint/no-unused-vars': [ - 'error', - { - 'argsIgnorePattern': '^_' - } - ] - } - } -); \ No newline at end of file + { + ignores: ['.vscode-test', 'out'], + }, + { + files: ['**/*.{js,mjs,cjs,ts,jsx,tsx}'], + }, + js.configs.recommended, + ...tseslint.configs.recommended, + ...tseslint.configs.stylistic, + { + plugins: { + '@stylistic': stylistic, + }, + rules: { + curly: 'warn', + '@stylistic/semi': ['warn', 'always'], + '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/array-type': 'off', + '@typescript-eslint/naming-convention': [ + 'warn', + { + selector: 'import', + format: ['camelCase', 'PascalCase'], + }, + ], + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + }, + ], + }, + }, +); diff --git a/package-lock.json b/package-lock.json index d7743d3..6cfafcd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,2085 +1,2102 @@ { - "name": "testdriver", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "testdriver", - "version": "0.1.0", - "dependencies": { - "@vscode/chat-extension-utils": "^0.0.0-alpha.1", - "@vscode/prompt-tsx": "^0.3.0-alpha.12", - "markdown-parser": "^0.0.8", - "vscode": "^1.1.37", - "ws": "^8.18.1" - }, - "devDependencies": { - "@eslint/js": "^9.13.0", - "@stylistic/eslint-plugin": "^2.9.0", - "@types/node": "^20", - "@types/vscode": "^1.95.0", - "@types/ws": "^8.18.0", - "eslint": "^9.13.0", - "typescript": "^5.8.2", - "typescript-eslint": "^8.26.0" - }, - "engines": { - "vscode": "^1.95.0" - } - }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", - "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.4", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz", - "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", - "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/js": { - "version": "9.15.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.15.0.tgz", - "integrity": "sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz", - "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@stylistic/eslint-plugin": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.9.0.tgz", - "integrity": "sha512-OrDyFAYjBT61122MIY1a3SfEgy3YCMgt2vL4eoPmvTwDBwyQhAXurxNQznlRD/jESNfYWfID8Ej+31LljvF7Xg==", - "dev": true, - "dependencies": { - "@typescript-eslint/utils": "^8.8.0", - "eslint-visitor-keys": "^4.1.0", - "espree": "^10.2.0", - "estraverse": "^5.3.0", - "picomatch": "^4.0.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "peerDependencies": { - "eslint": ">=8.40.0" - } - }, - "node_modules/@stylistic/eslint-plugin/node_modules/eslint-visitor-keys": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", - "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@stylistic/eslint-plugin/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true - }, - "node_modules/@types/node": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz", - "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/vscode": { - "version": "1.95.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.95.0.tgz", - "integrity": "sha512-0LBD8TEiNbet3NvWsmn59zLzOFu/txSlGxnv5yAFHCrhG9WvAnR3IvfHzMOs2aeWqgvNjq9pO99IUw8d3n+unw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.0.tgz", - "integrity": "sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.26.0", - "@typescript-eslint/type-utils": "8.26.0", - "@typescript-eslint/utils": "8.26.0", - "@typescript-eslint/visitor-keys": "8.26.0", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.0.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.26.0.tgz", - "integrity": "sha512-mNtXP9LTVBy14ZF3o7JG69gRPBK/2QWtQd0j0oH26HcY/foyJJau6pNUez7QrM5UHnSvwlQcJXKsk0I99B9pOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.26.0", - "@typescript-eslint/types": "8.26.0", - "@typescript-eslint/typescript-estree": "8.26.0", - "@typescript-eslint/visitor-keys": "8.26.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.0.tgz", - "integrity": "sha512-E0ntLvsfPqnPwng8b8y4OGuzh/iIOm2z8U3S9zic2TeMLW61u5IH2Q1wu0oSTkfrSzwbDJIB/Lm8O3//8BWMPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.26.0", - "@typescript-eslint/visitor-keys": "8.26.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.26.0.tgz", - "integrity": "sha512-ruk0RNChLKz3zKGn2LwXuVoeBcUMh+jaqzN461uMMdxy5H9epZqIBtYj7UiPXRuOpaALXGbmRuZQhmwHhaS04Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "8.26.0", - "@typescript-eslint/utils": "8.26.0", - "debug": "^4.3.4", - "ts-api-utils": "^2.0.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.0.tgz", - "integrity": "sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.0.tgz", - "integrity": "sha512-tiJ1Hvy/V/oMVRTbEOIeemA2XoylimlDQ03CgPPNaHYZbpsc78Hmngnt+WXZfJX1pjQ711V7g0H7cSJThGYfPQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.26.0", - "@typescript-eslint/visitor-keys": "8.26.0", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.0.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.0.tgz", - "integrity": "sha512-2L2tU3FVwhvU14LndnQCA2frYC8JnPDVKyQtWFPf8IYFMt/ykEN1bPolNhNbCVgOmdzTlWdusCTKA/9nKrf8Ig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.26.0", - "@typescript-eslint/types": "8.26.0", - "@typescript-eslint/typescript-estree": "8.26.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.0.tgz", - "integrity": "sha512-2z8JQJWAzPdDd51dRQ/oqIJxe99/hoLIqmf8RMCAJQtYDc535W/Jt2+RTP4bP0aKeBG1F65yjIZuczOXCmbWwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.26.0", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@vscode/chat-extension-utils": { - "version": "0.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/@vscode/chat-extension-utils/-/chat-extension-utils-0.0.0-alpha.1.tgz", - "integrity": "sha512-49eYur98d1iukPEQqMYQL4lJgaKnM0QFQB4/BFIFvuuKM+Kug2KNE/TSIJJQXrp5CrP0kDOmIIXvTnNRPtO2vg==", - "license": "MIT", - "dependencies": { - "@vscode/prompt-tsx": "^0.3.0-alpha.13" - } - }, - "node_modules/@vscode/prompt-tsx": { - "version": "0.3.0-alpha.13", - "resolved": "https://registry.npmjs.org/@vscode/prompt-tsx/-/prompt-tsx-0.3.0-alpha.13.tgz", - "integrity": "sha512-0m9Hy2VqfGcFgXmY7xFV1nYngoq2zm2Wy/3YdesmR6bOwFrJed9xW87y43Ax7UFVHwtjZkpjn4M9HbFvxvzdWA==", - "license": "MIT" - }, - "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "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, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "license": "ISC" - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "license": "MIT" - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "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, - "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/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, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "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 - }, - "node_modules/commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "license": "MIT" - }, - "node_modules/es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", - "license": "MIT", - "dependencies": { - "es6-promise": "^4.0.3" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "9.13.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.13.0.tgz", - "integrity": "sha512-EYZK6SX6zjFHST/HRytOdA/zE72Cq/bfw45LSyuwrdvcclb/gqV8RRQxywOBEWO2+WDpva6UZa4CcDeJKzUCFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.11.0", - "@eslint/config-array": "^0.18.0", - "@eslint/core": "^0.7.0", - "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.13.0", - "@eslint/plugin-kit": "^0.2.0", - "@humanfs/node": "^0.16.5", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.3.1", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.1.0", - "eslint-visitor-keys": "^4.1.0", - "espree": "^10.2.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-scope": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", - "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/@eslint/js": { - "version": "9.13.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.13.0.tgz", - "integrity": "sha512-IFLyoY4d72Z5y/6o/BazFBezupzI/taV8sGumxTAVw3lXG9A6md1Dc34T9s1FoD/an9pJH8RHbAxsaEbBed9lA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.14.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "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, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "license": "ISC" - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, - "node_modules/growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "license": "MIT", - "engines": { - "node": ">=4.x" - } - }, - "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, - "engines": { - "node": ">=8" - } - }, - "node_modules/he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha512-z/GDPjlRMNOa2XJiB4em8wJpuuBfrFOlYKTZxtpkdr1uPdibHI8rYA3MY0KDObpVyaes0e/aunid/t88ZI2EKA==", - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, - "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "license": "MIT", - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/markdown-parser": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/markdown-parser/-/markdown-parser-0.0.8.tgz", - "integrity": "sha512-NgGA5NFm5qSQRNjRsL71UXdxc1Cif0mkMuj3GvXfYOMuCj5LH7hig96n8IakimqGLgf6md0Kz27QWNoAzVgf7A==", - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==", - "license": "MIT" - }, - "node_modules/mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==", - "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", - "license": "MIT", - "dependencies": { - "minimist": "0.0.8" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", - "license": "MIT", - "dependencies": { - "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.5", - "he": "1.1.1", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "supports-color": "5.4.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/mocha/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/mocha/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/mocha/node_modules/glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mocha/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mocha/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, - "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-api-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz", - "integrity": "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/typescript": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", - "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-eslint": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.26.0.tgz", - "integrity": "sha512-PtVz9nAnuNJuAVeUFvwztjuUgSnJInODAUx47VDwWPXzd5vismPOtPtt83tzNXyOjVQbPRp786D6WFW/M2koIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.26.0", - "@typescript-eslint/parser": "8.26.0", - "@typescript-eslint/utils": "8.26.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/vscode": { - "version": "1.1.37", - "resolved": "https://registry.npmjs.org/vscode/-/vscode-1.1.37.tgz", - "integrity": "sha512-vJNj6IlN7IJPdMavlQa1KoFB3Ihn06q1AiN3ZFI/HfzPNzbKZWPPuiU+XkpNOfGU5k15m4r80nxNPlM7wcc0wg==", - "deprecated": "This package is deprecated in favor of @types/vscode and vscode-test. For more information please read: https://code.visualstudio.com/updates/v1_36#_splitting-vscode-package-into-typesvscode-and-vscodetest", - "license": "MIT", - "dependencies": { - "glob": "^7.1.2", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "mocha": "^5.2.0", - "semver": "^5.4.1", - "source-map-support": "^0.5.0", - "vscode-test": "^0.4.1" - }, - "bin": { - "vscode-install": "bin/install" - }, - "engines": { - "node": ">=8.9.3" - } - }, - "node_modules/vscode-test": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/vscode-test/-/vscode-test-0.4.3.tgz", - "integrity": "sha512-EkMGqBSefZH2MgW65nY05rdRSko15uvzq4VAPM5jVmwYuFQKE7eikKXNJDRxL+OITXHB6pI+a3XqqD32Y3KC5w==", - "deprecated": "This package has been renamed to @vscode/test-electron, please update to the new name", - "license": "MIT", - "dependencies": { - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.1" - }, - "engines": { - "node": ">=8.9.3" - } - }, - "node_modules/vscode-test/node_modules/agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "license": "MIT", - "dependencies": { - "es6-promisify": "^5.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/vscode-test/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/vscode-test/node_modules/http-proxy-agent": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", - "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", - "license": "MIT", - "dependencies": { - "agent-base": "4", - "debug": "3.1.0" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/vscode-test/node_modules/https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "license": "MIT", - "dependencies": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/vscode-test/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/vscode/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "node_modules/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", - "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/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } + "name": "testdriver", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "testdriver", + "version": "0.1.0", + "dependencies": { + "@vscode/chat-extension-utils": "^0.0.0-alpha.1", + "@vscode/prompt-tsx": "^0.3.0-alpha.12", + "markdown-parser": "^0.0.8", + "vscode": "^1.1.37", + "ws": "^8.18.1" + }, + "devDependencies": { + "@eslint/js": "^9.13.0", + "@stylistic/eslint-plugin": "^2.9.0", + "@types/node": "^20", + "@types/vscode": "^1.95.0", + "@types/ws": "^8.18.0", + "eslint": "^9.13.0", + "prettier": "^3.5.3", + "typescript": "^5.8.2", + "typescript-eslint": "^8.26.0" + }, + "engines": { + "vscode": "^1.95.0" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", + "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz", + "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", + "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.15.0.tgz", + "integrity": "sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz", + "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@stylistic/eslint-plugin": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.9.0.tgz", + "integrity": "sha512-OrDyFAYjBT61122MIY1a3SfEgy3YCMgt2vL4eoPmvTwDBwyQhAXurxNQznlRD/jESNfYWfID8Ej+31LljvF7Xg==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "^8.8.0", + "eslint-visitor-keys": "^4.1.0", + "espree": "^10.2.0", + "estraverse": "^5.3.0", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/eslint-visitor-keys": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", + "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz", + "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/vscode": { + "version": "1.95.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.95.0.tgz", + "integrity": "sha512-0LBD8TEiNbet3NvWsmn59zLzOFu/txSlGxnv5yAFHCrhG9WvAnR3IvfHzMOs2aeWqgvNjq9pO99IUw8d3n+unw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.0.tgz", + "integrity": "sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.26.0", + "@typescript-eslint/type-utils": "8.26.0", + "@typescript-eslint/utils": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.26.0.tgz", + "integrity": "sha512-mNtXP9LTVBy14ZF3o7JG69gRPBK/2QWtQd0j0oH26HcY/foyJJau6pNUez7QrM5UHnSvwlQcJXKsk0I99B9pOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.26.0", + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/typescript-estree": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.0.tgz", + "integrity": "sha512-E0ntLvsfPqnPwng8b8y4OGuzh/iIOm2z8U3S9zic2TeMLW61u5IH2Q1wu0oSTkfrSzwbDJIB/Lm8O3//8BWMPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.26.0.tgz", + "integrity": "sha512-ruk0RNChLKz3zKGn2LwXuVoeBcUMh+jaqzN461uMMdxy5H9epZqIBtYj7UiPXRuOpaALXGbmRuZQhmwHhaS04Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.26.0", + "@typescript-eslint/utils": "8.26.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.0.tgz", + "integrity": "sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.0.tgz", + "integrity": "sha512-tiJ1Hvy/V/oMVRTbEOIeemA2XoylimlDQ03CgPPNaHYZbpsc78Hmngnt+WXZfJX1pjQ711V7g0H7cSJThGYfPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.0.tgz", + "integrity": "sha512-2L2tU3FVwhvU14LndnQCA2frYC8JnPDVKyQtWFPf8IYFMt/ykEN1bPolNhNbCVgOmdzTlWdusCTKA/9nKrf8Ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.26.0", + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/typescript-estree": "8.26.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.0.tgz", + "integrity": "sha512-2z8JQJWAzPdDd51dRQ/oqIJxe99/hoLIqmf8RMCAJQtYDc535W/Jt2+RTP4bP0aKeBG1F65yjIZuczOXCmbWwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.26.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vscode/chat-extension-utils": { + "version": "0.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/@vscode/chat-extension-utils/-/chat-extension-utils-0.0.0-alpha.1.tgz", + "integrity": "sha512-49eYur98d1iukPEQqMYQL4lJgaKnM0QFQB4/BFIFvuuKM+Kug2KNE/TSIJJQXrp5CrP0kDOmIIXvTnNRPtO2vg==", + "license": "MIT", + "dependencies": { + "@vscode/prompt-tsx": "^0.3.0-alpha.13" + } + }, + "node_modules/@vscode/prompt-tsx": { + "version": "0.3.0-alpha.13", + "resolved": "https://registry.npmjs.org/@vscode/prompt-tsx/-/prompt-tsx-0.3.0-alpha.13.tgz", + "integrity": "sha512-0m9Hy2VqfGcFgXmY7xFV1nYngoq2zm2Wy/3YdesmR6bOwFrJed9xW87y43Ax7UFVHwtjZkpjn4M9HbFvxvzdWA==", + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "license": "ISC" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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, + "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/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "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 + }, + "node_modules/commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "license": "MIT" + }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", + "license": "MIT", + "dependencies": { + "es6-promise": "^4.0.3" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.13.0.tgz", + "integrity": "sha512-EYZK6SX6zjFHST/HRytOdA/zE72Cq/bfw45LSyuwrdvcclb/gqV8RRQxywOBEWO2+WDpva6UZa4CcDeJKzUCFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.18.0", + "@eslint/core": "^0.7.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.13.0", + "@eslint/plugin-kit": "^0.2.0", + "@humanfs/node": "^0.16.5", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.3.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.1.0", + "eslint-visitor-keys": "^4.1.0", + "espree": "^10.2.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/@eslint/js": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.13.0.tgz", + "integrity": "sha512-IFLyoY4d72Z5y/6o/BazFBezupzI/taV8sGumxTAVw3lXG9A6md1Dc34T9s1FoD/an9pJH8RHbAxsaEbBed9lA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "license": "MIT", + "engines": { + "node": ">=4.x" + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha512-z/GDPjlRMNOa2XJiB4em8wJpuuBfrFOlYKTZxtpkdr1uPdibHI8rYA3MY0KDObpVyaes0e/aunid/t88ZI2EKA==", + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "license": "MIT", + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/markdown-parser": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/markdown-parser/-/markdown-parser-0.0.8.tgz", + "integrity": "sha512-NgGA5NFm5qSQRNjRsL71UXdxc1Cif0mkMuj3GvXfYOMuCj5LH7hig96n8IakimqGLgf6md0Kz27QWNoAzVgf7A==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==", + "license": "MIT" + }, + "node_modules/mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "license": "MIT", + "dependencies": { + "minimist": "0.0.8" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "license": "MIT", + "dependencies": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/mocha/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/mocha/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mocha/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mocha/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz", + "integrity": "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.26.0.tgz", + "integrity": "sha512-PtVz9nAnuNJuAVeUFvwztjuUgSnJInODAUx47VDwWPXzd5vismPOtPtt83tzNXyOjVQbPRp786D6WFW/M2koIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.26.0", + "@typescript-eslint/parser": "8.26.0", + "@typescript-eslint/utils": "8.26.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vscode": { + "version": "1.1.37", + "resolved": "https://registry.npmjs.org/vscode/-/vscode-1.1.37.tgz", + "integrity": "sha512-vJNj6IlN7IJPdMavlQa1KoFB3Ihn06q1AiN3ZFI/HfzPNzbKZWPPuiU+XkpNOfGU5k15m4r80nxNPlM7wcc0wg==", + "deprecated": "This package is deprecated in favor of @types/vscode and vscode-test. For more information please read: https://code.visualstudio.com/updates/v1_36#_splitting-vscode-package-into-typesvscode-and-vscodetest", + "license": "MIT", + "dependencies": { + "glob": "^7.1.2", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "mocha": "^5.2.0", + "semver": "^5.4.1", + "source-map-support": "^0.5.0", + "vscode-test": "^0.4.1" + }, + "bin": { + "vscode-install": "bin/install" + }, + "engines": { + "node": ">=8.9.3" + } + }, + "node_modules/vscode-test": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/vscode-test/-/vscode-test-0.4.3.tgz", + "integrity": "sha512-EkMGqBSefZH2MgW65nY05rdRSko15uvzq4VAPM5jVmwYuFQKE7eikKXNJDRxL+OITXHB6pI+a3XqqD32Y3KC5w==", + "deprecated": "This package has been renamed to @vscode/test-electron, please update to the new name", + "license": "MIT", + "dependencies": { + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.1" + }, + "engines": { + "node": ">=8.9.3" + } + }, + "node_modules/vscode-test/node_modules/agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "license": "MIT", + "dependencies": { + "es6-promisify": "^5.0.0" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/vscode-test/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/vscode-test/node_modules/http-proxy-agent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", + "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", + "license": "MIT", + "dependencies": { + "agent-base": "4", + "debug": "3.1.0" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/vscode-test/node_modules/https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "license": "MIT", + "dependencies": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/vscode-test/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/vscode/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "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/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } } diff --git a/package.json b/package.json index 487f466..7ee1c43 100644 --- a/package.json +++ b/package.json @@ -1,192 +1,194 @@ { - "name": "testdriver", - "publisher": "vscode-samples", - "displayName": "TestDriver VS Code Extension", - "description": "AI QA Agents", - "repository": { - "type": "git", - "url": "https://github.com/Microsoft/vscode-extension-samples" - }, - "version": "0.1.0", - "engines": { - "vscode": "^1.95.0" - }, - "categories": [ - "AI", - "Chat" - ], - "activationEvents": [ - "*" - ], - "contributes": { - "chatParticipants": [ - { - "id": "testdriver.driver", - "fullName": "TestDriver", - "name": "testdriver", - "description": "What can I test for you?", - "isSticky": true, - "commands": [ - { - "name": "dry", - "description": "Generate TestDriver steps using a desktop screenshot", - "disambiguation": [ - { - "category": "exec", - "description": "The user wants to try steps before executing them.", - "examples": [ - "open browser and navigate to airbnb.com", - "fill out this form" - ] - } - ] - }, - { - "name": "try", - "description": "Generate TestDriver steps using a desktop screenshot", - "disambiguation": [ - { - "category": "exec", - "description": "The user wants to automate a workflow in one step", - "examples": [ - "open browser and navigate to airbnb.com", - "fill out this form" - ] - } - ] - } - ] - }, - { - "id": "chat-tools-sample.tools", - "fullName": "Tool User", - "name": "tools", - "description": "I use tools", - "isSticky": true, - "commands": [ - { - "name": "list", - "description": "List all available tools" - }, - { - "name": "all", - "description": "Use all registered tools. By default, only this extension's tools are used." - } - ] - }, - { - "id": "chat-tools-sample.catTools", - "fullName": "Cat (Tools)", - "name": "catTools", - "description": "I use tools, implemented using @vscode/chat-extension-utils, and am also a cat", - "isSticky": true, - "commands": [ - { - "name": "all", - "description": "Use all registered tools. By default, only this extension's tools are used." - } - ] - } - ], - "languageModelTools": [ - { - "name": "chat-tools-sample_tabCount", - "tags": [ - "editors", - "chat-tools-sample" - ], - "toolReferenceName": "tabCount", - "displayName": "Tab Count", - "modelDescription": "The number of active tabs in a tab group", - "icon": "$(files)", - "inputSchema": { - "type": "object", - "properties": { - "tabGroup": { - "type": "number", - "description": "The index of the tab group to check. This is optional- if not specified, the active tab group will be checked.", - "default": 0 - } - } - } - }, - { - "name": "chat-tools-sample_findFiles", - "tags": [ - "files", - "search", - "chat-tools-sample" - ], - "displayName": "Find Files", - "modelDescription": "Search for files in the current workspace", - "inputSchema": { - "type": "object", - "properties": { - "pattern": { - "type": "string", - "description": "Search for files that match this glob pattern" - } - }, - "required": [ - "pattern" - ] - } - }, - { - "name": "chat-tools-sample_runInTerminal", - "tags": [ - "terminal", - "chat-tools-sample" - ], - "displayName": "Run in Terminal", - "modelDescription": "Run a command in a terminal and return the output", - "inputSchema": { - "type": "object", - "properties": { - "command": { - "type": "string", - "description": "The command to run" - } - }, - "required": [ - "command" - ] - } - } - ], - "commands": [ - { - "command": "dry", - "title": "Analyze screen and generate test steps, but don't run them." - }, - { - "command": "try", - "title": "Analyze screen and generate test steps, then run." - } - ] - }, - "main": "./out/extension.js", - "scripts": { - "vscode:prepublish": "npm run compile", - "compile": "tsc -p ./", - "lint": "eslint", - "watch": "tsc -watch -p ./" - }, - "dependencies": { - "@vscode/chat-extension-utils": "^0.0.0-alpha.1", - "@vscode/prompt-tsx": "^0.3.0-alpha.12", - "markdown-parser": "^0.0.8", - "vscode": "^1.1.37", - "ws": "^8.18.1" - }, - "devDependencies": { - "@eslint/js": "^9.13.0", - "@stylistic/eslint-plugin": "^2.9.0", - "@types/node": "^20", - "@types/vscode": "^1.95.0", - "@types/ws": "^8.18.0", - "eslint": "^9.13.0", - "typescript": "^5.8.2", - "typescript-eslint": "^8.26.0" - } + "name": "testdriver", + "publisher": "vscode-samples", + "displayName": "TestDriver VS Code Extension", + "description": "AI QA Agents", + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/vscode-extension-samples" + }, + "version": "0.1.0", + "engines": { + "vscode": "^1.95.0" + }, + "categories": [ + "AI", + "Chat" + ], + "activationEvents": [ + "*" + ], + "contributes": { + "chatParticipants": [ + { + "id": "testdriver.driver", + "fullName": "TestDriver", + "name": "testdriver", + "description": "What can I test for you?", + "isSticky": true, + "commands": [ + { + "name": "dry", + "description": "Generate TestDriver steps using a desktop screenshot", + "disambiguation": [ + { + "category": "exec", + "description": "The user wants to try steps before executing them.", + "examples": [ + "open browser and navigate to airbnb.com", + "fill out this form" + ] + } + ] + }, + { + "name": "try", + "description": "Generate TestDriver steps using a desktop screenshot", + "disambiguation": [ + { + "category": "exec", + "description": "The user wants to automate a workflow in one step", + "examples": [ + "open browser and navigate to airbnb.com", + "fill out this form" + ] + } + ] + } + ] + }, + { + "id": "chat-tools-sample.tools", + "fullName": "Tool User", + "name": "tools", + "description": "I use tools", + "isSticky": true, + "commands": [ + { + "name": "list", + "description": "List all available tools" + }, + { + "name": "all", + "description": "Use all registered tools. By default, only this extension's tools are used." + } + ] + }, + { + "id": "chat-tools-sample.catTools", + "fullName": "Cat (Tools)", + "name": "catTools", + "description": "I use tools, implemented using @vscode/chat-extension-utils, and am also a cat", + "isSticky": true, + "commands": [ + { + "name": "all", + "description": "Use all registered tools. By default, only this extension's tools are used." + } + ] + } + ], + "languageModelTools": [ + { + "name": "chat-tools-sample_tabCount", + "tags": [ + "editors", + "chat-tools-sample" + ], + "toolReferenceName": "tabCount", + "displayName": "Tab Count", + "modelDescription": "The number of active tabs in a tab group", + "icon": "$(files)", + "inputSchema": { + "type": "object", + "properties": { + "tabGroup": { + "type": "number", + "description": "The index of the tab group to check. This is optional- if not specified, the active tab group will be checked.", + "default": 0 + } + } + } + }, + { + "name": "chat-tools-sample_findFiles", + "tags": [ + "files", + "search", + "chat-tools-sample" + ], + "displayName": "Find Files", + "modelDescription": "Search for files in the current workspace", + "inputSchema": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Search for files that match this glob pattern" + } + }, + "required": [ + "pattern" + ] + } + }, + { + "name": "chat-tools-sample_runInTerminal", + "tags": [ + "terminal", + "chat-tools-sample" + ], + "displayName": "Run in Terminal", + "modelDescription": "Run a command in a terminal and return the output", + "inputSchema": { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "The command to run" + } + }, + "required": [ + "command" + ] + } + } + ], + "commands": [ + { + "command": "dry", + "title": "Analyze screen and generate test steps, but don't run them." + }, + { + "command": "try", + "title": "Analyze screen and generate test steps, then run." + } + ] + }, + "main": "./out/extension.js", + "scripts": { + "vscode:prepublish": "npm run compile", + "compile": "tsc -p ./", + "format": "prettier --write .", + "lint": "eslint", + "watch": "tsc -watch -p ./" + }, + "dependencies": { + "@vscode/chat-extension-utils": "^0.0.0-alpha.1", + "@vscode/prompt-tsx": "^0.3.0-alpha.12", + "markdown-parser": "^0.0.8", + "vscode": "^1.1.37", + "ws": "^8.18.1" + }, + "devDependencies": { + "@eslint/js": "^9.13.0", + "@stylistic/eslint-plugin": "^2.9.0", + "@types/node": "^20", + "@types/vscode": "^1.95.0", + "@types/ws": "^8.18.0", + "eslint": "^9.13.0", + "prettier": "^3.5.3", + "typescript": "^5.8.2", + "typescript-eslint": "^8.26.0" + } } diff --git a/src/cli.ts b/src/cli.ts index 0ce6bdb..d4a2a06 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -15,22 +15,27 @@ interface WebSocketMessage { } // Connect to WebSocket server -const terminal = vscode.window.createTerminal("TestDriver"); +const terminal = vscode.window.createTerminal('TestDriver'); terminal.show(); -terminal.sendText(`/Users/ianjennings/Development/testdriverai/index.js ${tempFile}`); - -const callTDCLI = function (command: string, stream: vscode.ChatResponseStream): Promise { - +terminal.sendText( + `/Users/ianjennings/Development/testdriverai/index.js ${tempFile}`, +); + +const callTDCLI = function ( + command: string, + stream: vscode.ChatResponseStream, +): Promise { const ws = new WebSocket('ws://localhost:8080'); return new Promise((resolve, reject) => { - ws.on('open', () => { console.log('WebSocket connection opened'); - ws.send(JSON.stringify({ - event: "input", - data: command - })); + ws.send( + JSON.stringify({ + event: 'input', + data: command, + }), + ); }); ws.on('close', () => { @@ -49,31 +54,25 @@ const callTDCLI = function (command: string, stream: vscode.ChatResponseStream): let hasBlock = false; ws.on('message', (data: string) => { - let parsedData: WebSocketMessage = JSON.parse(data); if (parsedData.event === 'output' && parsedData.message) { - let nextmsg = parsedData.message; for (const char of parsedData.message) { - buff += char; if (buff.slice(-3) === '```') { + console.log('yml detected'); - console.log('yml detected'); - - insideYML = !insideYML; - - if (insideYML) { - console.log('pushing'); - nextmsg = nextmsg + ''; - console.log(nextmsg); - YMLever = true; - } + insideYML = !insideYML; + if (insideYML) { + console.log('pushing'); + nextmsg = nextmsg + ''; + console.log(nextmsg); + YMLever = true; + } } - } console.log(buff); @@ -83,38 +82,33 @@ const callTDCLI = function (command: string, stream: vscode.ChatResponseStream): stream.markdown(nextmsg); if (!insideYML && YMLever) { - // Render a button to trigger a VS Code command stream.button({ command: 'testdriver.codeblock.run', title: vscode.l10n.t('Run Steps'), - arguments: [tempFile] // Send the YML code as an argument + arguments: [tempFile], // Send the YML code as an argument }); YMLever = false; // Reset YMLever to handle multiple code blocks - } - } if (parsedData.event === 'done') { - resolve({ yml: null, }); - } ws.on('close', () => { console.log('WebSocket connection closed'); - reject(new Error('WebSocket connection closed before receiving done event')); + reject( + new Error('WebSocket connection closed before receiving done event'), + ); }); - }); - }); -} +}; export default { - exec: callTDCLI + exec: callTDCLI, }; diff --git a/src/extension.ts b/src/extension.ts index 79b5556..57befaa 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,53 +1,72 @@ import * as vscode from 'vscode'; import { registerSimpleParticipant } from './simple'; -export function deactivate() { } +export function deactivate() {} -import { getContentFromFilesystem, TestCase, testData, TestFile } from './testTree'; +import { + getContentFromFilesystem, + TestCase, + testData, + TestFile, +} from './testTree'; export async function activate(context: vscode.ExtensionContext) { - registerSimpleParticipant(context); - const ctrl = vscode.tests.createTestController('mathTestController', 'Markdown Math'); - context.subscriptions.push(ctrl); - - const fileChangedEmitter = new vscode.EventEmitter(); - const watchingTests = new Map(); - fileChangedEmitter.event(uri => { - if (watchingTests.has('ALL')) { - startTestRun(new vscode.TestRunRequest(undefined, undefined, watchingTests.get('ALL'), true)); - return; - } - - const include: vscode.TestItem[] = []; - let profile: vscode.TestRunProfile | undefined; - for (const [item, thisProfile] of watchingTests) { - const cast = item as vscode.TestItem; - if (cast.uri?.toString() == uri.toString()) { - include.push(cast); - profile = thisProfile; - } - } - - if (include.length) { - startTestRun(new vscode.TestRunRequest(include, undefined, profile, true)); - } - }); + const ctrl = vscode.tests.createTestController( + 'mathTestController', + 'Markdown Math', + ); + context.subscriptions.push(ctrl); + + const fileChangedEmitter = new vscode.EventEmitter(); + const watchingTests = new Map< + vscode.TestItem | 'ALL', + vscode.TestRunProfile | undefined + >(); + fileChangedEmitter.event((uri) => { + if (watchingTests.has('ALL')) { + startTestRun( + new vscode.TestRunRequest( + undefined, + undefined, + watchingTests.get('ALL'), + true, + ), + ); + return; + } + + const include: vscode.TestItem[] = []; + let profile: vscode.TestRunProfile | undefined; + for (const [item, thisProfile] of watchingTests) { + const cast = item as vscode.TestItem; + if (cast.uri?.toString() == uri.toString()) { + include.push(cast); + profile = thisProfile; + } + } + + if (include.length) { + startTestRun( + new vscode.TestRunRequest(include, undefined, profile, true), + ); + } + }); async function runHandler( shouldDebug: boolean, request: vscode.TestRunRequest, - token: vscode.CancellationToken + token: vscode.CancellationToken, ) { const run = controller.createTestRun(request); const queue: vscode.TestItem[] = []; // Loop through all included tests, or all known tests, and add them to our queue if (request.include) { - request.include.forEach(test => queue.push(test)); + request.include.forEach((test) => queue.push(test)); } else { - controller.items.forEach(test => queue.push(test)); + controller.items.forEach((test) => queue.push(test)); } // For every test that was queued, try to run it. Call run.passed() or run.failed(). @@ -76,212 +95,270 @@ export async function activate(context: vscode.ExtensionContext) { await assertTestPasses(test); run.passed(test, Date.now() - start); } catch (e) { - run.failed(test, new vscode.TestMessage(e.message), Date.now() - start); + run.failed( + test, + new vscode.TestMessage(e.message), + Date.now() - start, + ); } break; } - test.children.forEach(test => queue.push(test)); + test.children.forEach((test) => queue.push(test)); } // Make sure to end the run after all tests have been executed: run.end(); } + const startTestRun = (request: vscode.TestRunRequest) => { + const queue: { test: vscode.TestItem; data: TestCase }[] = []; + const run = ctrl.createTestRun(request); + // map of file uris to statements on each line: + const coveredLines = new Map< + /* file uri */ string, + (vscode.StatementCoverage | undefined)[] + >(); + + const discoverTests = async (tests: Iterable) => { + for (const test of tests) { + if (request.exclude?.includes(test)) { + continue; + } + + const data = testData.get(test); + if (data instanceof TestCase) { + run.enqueued(test); + queue.push({ test, data }); + } else { + if (data instanceof TestFile && !data.didResolve) { + await data.updateFromDisk(ctrl, test); + } + + await discoverTests(gatherTestItems(test.children)); + } + + if ( + test.uri && + !coveredLines.has(test.uri.toString()) && + request.profile?.kind === vscode.TestRunProfileKind.Coverage + ) { + try { + const lines = (await getContentFromFilesystem(test.uri)).split( + '\n', + ); + coveredLines.set( + test.uri.toString(), + lines.map((lineText, lineNo) => + lineText.trim().length + ? new vscode.StatementCoverage( + 0, + new vscode.Position(lineNo, 0), + ) + : undefined, + ), + ); + } catch { + // ignored + } + } + } + }; - const startTestRun = (request: vscode.TestRunRequest) => { - const queue: { test: vscode.TestItem; data: TestCase }[] = []; - const run = ctrl.createTestRun(request); - // map of file uris to statements on each line: - const coveredLines = new Map(); - - const discoverTests = async (tests: Iterable) => { - for (const test of tests) { - if (request.exclude?.includes(test)) { - continue; - } - - const data = testData.get(test); - if (data instanceof TestCase) { - run.enqueued(test); - queue.push({ test, data }); - } else { - if (data instanceof TestFile && !data.didResolve) { - await data.updateFromDisk(ctrl, test); - } - - await discoverTests(gatherTestItems(test.children)); - } - - if (test.uri && !coveredLines.has(test.uri.toString()) && request.profile?.kind === vscode.TestRunProfileKind.Coverage) { - try { - const lines = (await getContentFromFilesystem(test.uri)).split('\n'); - coveredLines.set( - test.uri.toString(), - lines.map((lineText, lineNo) => - lineText.trim().length ? new vscode.StatementCoverage(0, new vscode.Position(lineNo, 0)) : undefined - ) - ); - } catch { - // ignored - } - } - } - }; - - const runTestQueue = async () => { - for (const { test, data } of queue) { - - run.appendOutput(`Running ${test.id}\r\n`); + const runTestQueue = async () => { + for (const { test, data } of queue) { + run.appendOutput(`Running ${test.id}\r\n`); if (run.token.isCancellationRequested) { - run.skipped(test); - } else { - run.started(test); - await data.run(test, run); - } + run.skipped(test); + } else { + run.started(test); + await data.run(test, run); + } - const lineNo = test.range!.start.line; - const fileCoverage = coveredLines.get(test.uri!.toString()); - const lineInfo = fileCoverage?.[lineNo]; + const lineNo = test.range!.start.line; + const fileCoverage = coveredLines.get(test.uri!.toString()); + const lineInfo = fileCoverage?.[lineNo]; if (lineInfo) { - (lineInfo.executed as number)++; - } - - run.appendOutput(`Completed ${test.id}\r\n`); - } - - for (const [uri, statements] of coveredLines) { - run.addCoverage(new MarkdownFileCoverage(uri, statements)); - } - - run.end(); - }; - - discoverTests(request.include ?? gatherTestItems(ctrl.items)).then(runTestQueue); - }; - - ctrl.refreshHandler = async () => { - await Promise.all(getWorkspaceTestPatterns().map(({ pattern }) => findInitialFiles(ctrl, pattern))); - }; - - ctrl.createRunProfile('Run Tests', vscode.TestRunProfileKind.Run, runHandler, true, undefined, true); - - const coverageProfile = ctrl.createRunProfile('Run with Coverage', vscode.TestRunProfileKind.Coverage, runHandler, true, undefined, true); - coverageProfile.loadDetailedCoverage = async (_testRun, coverage) => { - if (coverage instanceof MarkdownFileCoverage) { - return coverage.coveredLines.filter((l): l is vscode.StatementCoverage => !!l); - } - - return []; - }; - - ctrl.resolveHandler = async item => { - if (!item) { - context.subscriptions.push(...startWatchingWorkspace(ctrl, fileChangedEmitter)); - return; - } - - const data = testData.get(item); - if (data instanceof TestFile) { - await data.updateFromDisk(ctrl, item); - } - }; - - function updateNodeForDocument(e: vscode.TextDocument) { - if (e.uri.scheme !== 'file') { - return; - } - - if (!e.uri.path.endsWith('.yaml')) { - return; - } - - const { file, data } = getOrCreateFile(ctrl, e.uri); - data.updateFromContents(ctrl, e.getText(), file); - } - - for (const document of vscode.workspace.textDocuments) { - updateNodeForDocument(document); - } - - context.subscriptions.push( - vscode.workspace.onDidOpenTextDocument(updateNodeForDocument), - vscode.workspace.onDidChangeTextDocument(e => updateNodeForDocument(e.document)), - ); + (lineInfo.executed as number)++; + } + + run.appendOutput(`Completed ${test.id}\r\n`); + } + + for (const [uri, statements] of coveredLines) { + run.addCoverage(new MarkdownFileCoverage(uri, statements)); + } + + run.end(); + }; + + discoverTests(request.include ?? gatherTestItems(ctrl.items)).then( + runTestQueue, + ); + }; + + ctrl.refreshHandler = async () => { + await Promise.all( + getWorkspaceTestPatterns().map(({ pattern }) => + findInitialFiles(ctrl, pattern), + ), + ); + }; + + ctrl.createRunProfile( + 'Run Tests', + vscode.TestRunProfileKind.Run, + runHandler, + true, + undefined, + true, + ); + + const coverageProfile = ctrl.createRunProfile( + 'Run with Coverage', + vscode.TestRunProfileKind.Coverage, + runHandler, + true, + undefined, + true, + ); + coverageProfile.loadDetailedCoverage = async (_testRun, coverage) => { + if (coverage instanceof MarkdownFileCoverage) { + return coverage.coveredLines.filter( + (l): l is vscode.StatementCoverage => !!l, + ); + } + + return []; + }; + + ctrl.resolveHandler = async (item) => { + if (!item) { + context.subscriptions.push( + ...startWatchingWorkspace(ctrl, fileChangedEmitter), + ); + return; + } + + const data = testData.get(item); + if (data instanceof TestFile) { + await data.updateFromDisk(ctrl, item); + } + }; + + function updateNodeForDocument(e: vscode.TextDocument) { + if (e.uri.scheme !== 'file') { + return; + } + + if (!e.uri.path.endsWith('.yaml')) { + return; + } + + const { file, data } = getOrCreateFile(ctrl, e.uri); + data.updateFromContents(ctrl, e.getText(), file); + } + + for (const document of vscode.workspace.textDocuments) { + updateNodeForDocument(document); + } + + context.subscriptions.push( + vscode.workspace.onDidOpenTextDocument(updateNodeForDocument), + vscode.workspace.onDidChangeTextDocument((e) => + updateNodeForDocument(e.document), + ), + ); } function getOrCreateFile(controller: vscode.TestController, uri: vscode.Uri) { - const existing = controller.items.get(uri.toString()); - if (existing) { - return { file: existing, data: testData.get(existing) as TestFile }; - } + const existing = controller.items.get(uri.toString()); + if (existing) { + return { file: existing, data: testData.get(existing) as TestFile }; + } - const file = controller.createTestItem(uri.toString(), uri.path.split('/').pop()!, uri); - controller.items.add(file); + const file = controller.createTestItem( + uri.toString(), + uri.path.split('/').pop()!, + uri, + ); + controller.items.add(file); - const data = new TestFile(); - testData.set(file, data); + const data = new TestFile(); + testData.set(file, data); - file.canResolveChildren = true; - return { file, data }; + file.canResolveChildren = true; + return { file, data }; } function gatherTestItems(collection: vscode.TestItemCollection) { - const items: vscode.TestItem[] = []; - collection.forEach(item => items.push(item)); - return items; + const items: vscode.TestItem[] = []; + collection.forEach((item) => items.push(item)); + return items; } function getWorkspaceTestPatterns() { - if (!vscode.workspace.workspaceFolders) { - return []; - } - - return vscode.workspace.workspaceFolders.map(workspaceFolder => ({ - workspaceFolder, - pattern: new vscode.RelativePattern(workspaceFolder, 'testdriver/**/*.yaml'), - })); + if (!vscode.workspace.workspaceFolders) { + return []; + } + + return vscode.workspace.workspaceFolders.map((workspaceFolder) => ({ + workspaceFolder, + pattern: new vscode.RelativePattern( + workspaceFolder, + 'testdriver/**/*.yaml', + ), + })); } -async function findInitialFiles(controller: vscode.TestController, pattern: vscode.GlobPattern) { - for (const file of await vscode.workspace.findFiles(pattern)) { - getOrCreateFile(controller, file); - } +async function findInitialFiles( + controller: vscode.TestController, + pattern: vscode.GlobPattern, +) { + for (const file of await vscode.workspace.findFiles(pattern)) { + getOrCreateFile(controller, file); + } } -function startWatchingWorkspace(controller: vscode.TestController, fileChangedEmitter: vscode.EventEmitter) { - return getWorkspaceTestPatterns().map(({ pattern }) => { - const watcher = vscode.workspace.createFileSystemWatcher(pattern); - - watcher.onDidCreate(uri => { - getOrCreateFile(controller, uri); - fileChangedEmitter.fire(uri); - }); - watcher.onDidChange(async uri => { - const { file, data } = getOrCreateFile(controller, uri); - if (data.didResolve) { - await data.updateFromDisk(controller, file); - } - fileChangedEmitter.fire(uri); - }); - watcher.onDidDelete(uri => controller.items.delete(uri.toString())); - - findInitialFiles(controller, pattern); - - return watcher; - }); +function startWatchingWorkspace( + controller: vscode.TestController, + fileChangedEmitter: vscode.EventEmitter, +) { + return getWorkspaceTestPatterns().map(({ pattern }) => { + const watcher = vscode.workspace.createFileSystemWatcher(pattern); + + watcher.onDidCreate((uri) => { + getOrCreateFile(controller, uri); + fileChangedEmitter.fire(uri); + }); + watcher.onDidChange(async (uri) => { + const { file, data } = getOrCreateFile(controller, uri); + if (data.didResolve) { + await data.updateFromDisk(controller, file); + } + fileChangedEmitter.fire(uri); + }); + watcher.onDidDelete((uri) => controller.items.delete(uri.toString())); + + findInitialFiles(controller, pattern); + + return watcher; + }); } class MarkdownFileCoverage extends vscode.FileCoverage { - constructor(uri: string, public readonly coveredLines: (vscode.StatementCoverage | undefined)[]) { - super(vscode.Uri.parse(uri), new vscode.TestCoverageCount(0, 0)); - for (const line of coveredLines) { - if (line) { - this.statementCoverage.covered += line.executed ? 1 : 0; - this.statementCoverage.total++; - } - } - } + constructor( + uri: string, + public readonly coveredLines: (vscode.StatementCoverage | undefined)[], + ) { + super(vscode.Uri.parse(uri), new vscode.TestCoverageCount(0, 0)); + for (const line of coveredLines) { + if (line) { + this.statementCoverage.covered += line.executed ? 1 : 0; + this.statementCoverage.total++; + } + } + } } diff --git a/src/simple.ts b/src/simple.ts index e4c2c79..9dc4afd 100644 --- a/src/simple.ts +++ b/src/simple.ts @@ -9,9 +9,9 @@ vscode.commands.registerCommand('testdriver.codeblock.run', async (yaml) => { }); interface ICatChatResult extends vscode.ChatResult { - metadata: { - command: string; - } + metadata: { + command: string; + }; } // const ansiRegex = (({ onlyFirst = false } = {}) => { // const pattern = [ @@ -30,120 +30,132 @@ interface ICatChatResult extends vscode.ChatResult { // } export function registerSimpleParticipant(context: vscode.ExtensionContext) { + // Define a Cat chat handler. + const handler: vscode.ChatRequestHandler = async ( + request: vscode.ChatRequest, + context: vscode.ChatContext, + stream: vscode.ChatResponseStream, + token: vscode.CancellationToken, + ): Promise => { + // generate a temporary file + + if (request.command === 'dry') { + stream.progress('Looking at screen...'); + await cli.exec('/dry ' + request.prompt, stream); + } else if (request.command === 'try') { + stream.progress('Looking at screen...'); + await cli.exec(request.prompt, stream); + } else { + stream.progress('Staring my engine...'); + + console.log(context.history); + + try { + const messages = [vscode.LanguageModelChatMessage.User(spec)]; + + // get all the previous participant messages + const previousMessages = context.history.filter( + (h) => h instanceof vscode.ChatResponseTurn, + ); + + // add the previous messages to the messages array + previousMessages.forEach((m) => { + let fullMessage = ''; + m.response.forEach((r) => { + const mdPart = r as vscode.ChatResponseMarkdownPart; + fullMessage += mdPart.value.value; + }); + messages.push(vscode.LanguageModelChatMessage.Assistant(fullMessage)); + }); - // Define a Cat chat handler. - const handler: vscode.ChatRequestHandler = async (request: vscode.ChatRequest, context: vscode.ChatContext, stream: vscode.ChatResponseStream, token: vscode.CancellationToken): Promise => { - - // generate a temporary file - - if (request.command === 'dry') { - - stream.progress('Looking at screen...'); - await cli.exec('/dry ' + request.prompt, stream); - - } else if (request.command === 'try') { - - stream.progress('Looking at screen...'); - await cli.exec(request.prompt, stream); - - } else { - - stream.progress('Staring my engine...'); - - console.log(context.history) - - - - try { - const messages = [ - vscode.LanguageModelChatMessage.User(spec), ]; - - // get all the previous participant messages - const previousMessages = context.history.filter( - h => h instanceof vscode.ChatResponseTurn - ); - - // add the previous messages to the messages array - previousMessages.forEach(m => { - let fullMessage = ''; - m.response.forEach(r => { - const mdPart = r as vscode.ChatResponseMarkdownPart; - fullMessage += mdPart.value.value; - }); - messages.push(vscode.LanguageModelChatMessage.Assistant(fullMessage)); - }); - - // add in the user's message - messages.push(vscode.LanguageModelChatMessage.User(request.prompt)); - - // send the request - const chatResponse = await request.model.sendRequest(messages, {}, token); + // add in the user's message + messages.push(vscode.LanguageModelChatMessage.User(request.prompt)); - for await (const fragment of chatResponse.text) { - stream.markdown(fragment); - } + // send the request + const chatResponse = await request.model.sendRequest( + messages, + {}, + token, + ); - } catch (err) { - console.log('err', err); - handleError(logger, err, stream); + for await (const fragment of chatResponse.text) { + stream.markdown(fragment); } - + } catch (err) { + console.log('err', err); + handleError(logger, err, stream); } - }; - - // Chat participants appear as top-level options in the chat input - // when you type `@`, and can contribute sub-commands in the chat input - // that appear when you type `/`. - const td = vscode.chat.createChatParticipant(PARTICIPANT_ID, handler); - td.iconPath = vscode.Uri.joinPath(context.extensionUri, 'icon.png'); - td.followupProvider = { - provideFollowups(_result: ICatChatResult, _context: vscode.ChatContext, _token: vscode.CancellationToken) { - return [{ - prompt: 'let us play', - label: vscode.l10n.t('Run this step'), - command: 'play' - } satisfies vscode.ChatFollowup]; - } - }; - - const logger = vscode.env.createTelemetryLogger({ - sendEventData(eventName, data) { - // Capture event telemetry - console.log(`Event: ${eventName}`); - console.log(`Data: ${JSON.stringify(data)}`); - }, - sendErrorData(error, data) { - // Capture error telemetry - console.error(`Error: ${error}`); - console.error(`Data: ${JSON.stringify(data)}`); - } - }); - - context.subscriptions.push(td.onDidReceiveFeedback((feedback: vscode.ChatResultFeedback) => { - // Log chat result feedback to be able to compute the success matric of the participant - // unhelpful / totalRequests is a good success metric - logger.logUsage('chatResultFeedback', { - kind: feedback.kind - }); - })); - + } + }; + + // Chat participants appear as top-level options in the chat input + // when you type `@`, and can contribute sub-commands in the chat input + // that appear when you type `/`. + const td = vscode.chat.createChatParticipant(PARTICIPANT_ID, handler); + td.iconPath = vscode.Uri.joinPath(context.extensionUri, 'icon.png'); + td.followupProvider = { + provideFollowups( + _result: ICatChatResult, + _context: vscode.ChatContext, + _token: vscode.CancellationToken, + ) { + return [ + { + prompt: 'let us play', + label: vscode.l10n.t('Run this step'), + command: 'play', + } satisfies vscode.ChatFollowup, + ]; + }, + }; + + const logger = vscode.env.createTelemetryLogger({ + sendEventData(eventName, data) { + // Capture event telemetry + console.log(`Event: ${eventName}`); + console.log(`Data: ${JSON.stringify(data)}`); + }, + sendErrorData(error, data) { + // Capture error telemetry + console.error(`Error: ${error}`); + console.error(`Data: ${JSON.stringify(data)}`); + }, + }); + + context.subscriptions.push( + td.onDidReceiveFeedback((feedback: vscode.ChatResultFeedback) => { + // Log chat result feedback to be able to compute the success matric of the participant + // unhelpful / totalRequests is a good success metric + logger.logUsage('chatResultFeedback', { + kind: feedback.kind, + }); + }), + ); } // eslint-disable-next-line @typescript-eslint/no-explicit-any -function handleError(logger: vscode.TelemetryLogger, err: any, stream: vscode.ChatResponseStream): void { - // making the chat request might fail because - // - model does not exist - // - user consent not given - // - quote limits exceeded - logger.logError(err); - - if (err instanceof vscode.LanguageModelError) { - console.log(err.message, err.code, err.cause); - if (err.cause instanceof Error && err.cause.message.includes('off_topic')) { - stream.markdown(vscode.l10n.t('I\'m sorry, I can only explain computer science concepts.')); - } - } else { - // re-throw other errors so they show up in the UI - throw err; +function handleError( + logger: vscode.TelemetryLogger, + err: any, + stream: vscode.ChatResponseStream, +): void { + // making the chat request might fail because + // - model does not exist + // - user consent not given + // - quote limits exceeded + logger.logError(err); + + if (err instanceof vscode.LanguageModelError) { + console.log(err.message, err.code, err.cause); + if (err.cause instanceof Error && err.cause.message.includes('off_topic')) { + stream.markdown( + vscode.l10n.t( + "I'm sorry, I can only explain computer science concepts.", + ), + ); } + } else { + // re-throw other errors so they show up in the UI + throw err; + } } diff --git a/src/spec.ts b/src/spec.ts index a58deba..61da310 100644 --- a/src/spec.ts +++ b/src/spec.ts @@ -154,4 +154,4 @@ outputs: runs: using: node16 main: ./dist/index.js -` +`; diff --git a/src/testTree.ts b/src/testTree.ts index 743cac9..7a8a099 100644 --- a/src/testTree.ts +++ b/src/testTree.ts @@ -12,98 +12,109 @@ export const testData = new WeakMap(); let generationCounter = 0; export const getContentFromFilesystem = async (uri: vscode.Uri) => { - try { - const rawContent = await vscode.workspace.fs.readFile(uri); - return textDecoder.decode(rawContent); - } catch (e) { - console.warn(`Error providing tests for ${uri.fsPath}`, e); - return ''; - } + try { + const rawContent = await vscode.workspace.fs.readFile(uri); + return textDecoder.decode(rawContent); + } catch (e) { + console.warn(`Error providing tests for ${uri.fsPath}`, e); + return ''; + } }; export class TestFile { - public didResolve = false; - - public async updateFromDisk(controller: vscode.TestController, item: vscode.TestItem) { - try { - const content = await getContentFromFilesystem(item.uri!); - item.error = undefined; - this.updateFromContents(controller, content, item); - } catch (e) { - item.error = (e as Error).stack; - } - } - - /** - * Parses the tests from the input text, and updates the tests contained - * by this file to be those from the text, - */ - public updateFromContents(controller: vscode.TestController, content: string, item: vscode.TestItem) { - const ancestors = [{ item, children: [] as vscode.TestItem[] }]; - const thisGeneration = generationCounter++; - this.didResolve = true; - - const ascend = (depth: number) => { - while (ancestors.length > depth) { - const finished = ancestors.pop()!; - finished.item.children.replace(finished.children); - } - }; - - ascend(0); // finish and assign children for all remaining items - } + public didResolve = false; + + public async updateFromDisk( + controller: vscode.TestController, + item: vscode.TestItem, + ) { + try { + const content = await getContentFromFilesystem(item.uri!); + item.error = undefined; + this.updateFromContents(controller, content, item); + } catch (e) { + item.error = (e as Error).stack; + } + } + + /** + * Parses the tests from the input text, and updates the tests contained + * by this file to be those from the text, + */ + public updateFromContents( + controller: vscode.TestController, + content: string, + item: vscode.TestItem, + ) { + const ancestors = [{ item, children: [] as vscode.TestItem[] }]; + const thisGeneration = generationCounter++; + this.didResolve = true; + + const ascend = (depth: number) => { + while (ancestors.length > depth) { + const finished = ancestors.pop()!; + finished.item.children.replace(finished.children); + } + }; + + ascend(0); // finish and assign children for all remaining items + } } export class TestHeading { - constructor(public generation: number) { } + constructor(public generation: number) {} } type Operator = '+' | '-' | '*' | '/'; export class TestCase { - constructor( - private readonly a: number, - private readonly operator: Operator, - private readonly b: number, - private readonly expected: number, - public generation: number - ) { } + constructor( + private readonly a: number, + private readonly operator: Operator, + private readonly b: number, + private readonly expected: number, + public generation: number, + ) {} - getLabel() { - return `${this.a} ${this.operator} ${this.b} = ${this.expected}`; - } + getLabel() { + return `${this.a} ${this.operator} ${this.b} = ${this.expected}`; + } - async run(item: vscode.TestItem, options: vscode.TestRun): Promise { - const start = Date.now(); + async run(item: vscode.TestItem, options: vscode.TestRun): Promise { + const start = Date.now(); - console.log(item) + console.log(item); let run = await cli.exec(`/run ${item.uri}`); - console.log(run) + console.log(run); const actual = this.evaluate(); - const duration = Date.now() - start; - - if (actual === this.expected) { - options.passed(item, duration); - } else { - const message = vscode.TestMessage.diff(`Expected ${item.label}`, String(this.expected), String(actual)); - message.location = new vscode.Location(item.uri!, item.range!); - options.failed(item, message, duration); - } - } - - private evaluate() { - switch (this.operator) { - case '-': - return this.a - this.b; - case '+': - return this.a + this.b; - case '/': - return Math.floor(this.a / this.b); - case '*': - return this.a * this.b; - } - } + const duration = Date.now() - start; + + if (actual === this.expected) { + options.passed(item, duration); + } else { + const message = vscode.TestMessage.diff( + `Expected ${item.label}`, + String(this.expected), + String(actual), + ); + message.location = new vscode.Location(item.uri!, item.range!); + options.failed(item, message, duration); + } + } + + private evaluate() { + switch (this.operator) { + case '-': + return this.a - this.b; + case '+': + return this.a + this.b; + case '/': + return Math.floor(this.a / this.b); + case '*': + return this.a * this.b; + } + } } diff --git a/tsconfig.json b/tsconfig.json index 3b1697b..0595598 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,20 +1,18 @@ { - "compilerOptions": { - "module": "Node16", - "target": "ES2022", - "lib": [ - "ES2022" - ], - "sourceMap": true, - "rootDir": "src", - "strict": true, /* enable all strict type-checking options */ - "outDir": "out", - "jsx": "react", - "jsxFactory": "vscpp", - "jsxFragmentFactory": "vscppf" - /* Additional Checks */ - // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - // "noUnusedParameters": true, /* Report errors on unused parameters. */ - } + "compilerOptions": { + "module": "Node16", + "target": "ES2022", + "lib": ["ES2022"], + "sourceMap": true, + "rootDir": "src", + "strict": true /* enable all strict type-checking options */, + "outDir": "out", + "jsx": "react", + "jsxFactory": "vscpp", + "jsxFragmentFactory": "vscppf" + /* Additional Checks */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + } } From f9bb47e299a23aa8eb89abdb4690566c529ce6ff Mon Sep 17 00:00:00 2001 From: Yassine El Khattabi Date: Thu, 27 Mar 2025 13:41:40 +0000 Subject: [PATCH 02/32] Add extension init command --- package.json | 4 +++ src/simple.ts | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/utils.ts | 44 +++++++++++++++++++++++++++ 3 files changed, 131 insertions(+) create mode 100644 src/utils.ts diff --git a/package.json b/package.json index 7ee1c43..b056c0a 100644 --- a/package.json +++ b/package.json @@ -155,6 +155,10 @@ } ], "commands": [ + { + "command": "testdriver.init", + "title": "Initialize testdriver in the active workspace" + }, { "command": "dry", "title": "Analyze screen and generate test steps, but don't run them." diff --git a/src/simple.ts b/src/simple.ts index 9dc4afd..cae597f 100644 --- a/src/simple.ts +++ b/src/simple.ts @@ -1,13 +1,96 @@ +import * as fs from 'fs'; +import * as path from 'path'; import * as vscode from 'vscode'; import cli from './cli'; const PARTICIPANT_ID = 'testdriver.driver'; import spec from './spec'; +import { getActiveWorkspaceFolder, run } from './utils'; vscode.commands.registerCommand('testdriver.codeblock.run', async (yaml) => { vscode.window.showInformationMessage(`Running YML steps:`); await cli.exec(`/yaml ${yaml}`); }); +vscode.commands.registerCommand('testdriver.init', async () => { + let statusBar = vscode.window.setStatusBarMessage( + 'Initializing Testdriver...', + ); + + // Check workspace folder + const workspaceFolder = getActiveWorkspaceFolder(); + statusBar.dispose(); + + if (!workspaceFolder) { + await vscode.window.showErrorMessage('No workspace folder found'); + return; + } + + const folder = workspaceFolder.uri.fsPath; + console.log('folder', folder); + + // Check if npm is installed + statusBar = vscode.window.setStatusBarMessage('Checking npm...'); + const { error: npmError, stdout: npmVersion } = await run('npm -v', { + cwd: folder, + }); + statusBar.dispose(); + console.log('npmVersion', npmVersion); + if (npmError) { + await vscode.window.showErrorMessage('npm is not installed'); + return; + } + + // Check if testdriverai is installed + statusBar = vscode.window.setStatusBarMessage('Checking testdriverai...'); + const { + error: tdError, + stdout: tdStdout, + stderr: tdStderr, + } = await run('testdriverai --help', { cwd: folder }); + statusBar.dispose(); + + console.log({ error: tdError, stdout: tdStdout, stderr: tdStderr }); + if (tdError) { + statusBar = vscode.window.setStatusBarMessage('Installing testdriverai...'); + const { error: installError } = await run( + 'npm install -g testdriverai@beta', + { cwd: folder }, + ); + statusBar.dispose(); + if (installError) { + await vscode.window.showErrorMessage('Failed to install testdriverai'); + return; + } + } + + // Check if Testdriver is already initialized + if (fs.existsSync(path.join(folder, 'testdriver'))) { + await vscode.window.showInformationMessage( + 'Skipping, Testdriver is already initialized in this workspace', + ); + return; + } + + statusBar = vscode.window.setStatusBarMessage( + 'Initializing Testdriver project...', + ); + const terminal = vscode.window.createTerminal('Testdriver'); + const promise = new Promise((resolve) => { + const disposable = vscode.window.onDidEndTerminalShellExecution((event) => { + if (event.terminal === terminal) { + disposable.dispose(); + resolve(); + } + }); + }); + terminal.sendText('testdriverai init'); + terminal.show(); + + await promise.finally(() => { + statusBar.dispose(); + }); +}); + interface ICatChatResult extends vscode.ChatResult { metadata: { command: string; diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..2eba9ee --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,44 @@ +import { exec } from 'node:child_process'; +import * as vscode from 'vscode'; + +export function getActiveWorkspaceFolder(): vscode.WorkspaceFolder | undefined { + const workspaceFolders = vscode.workspace.workspaceFolders; + + // No workspace folders open + if (!workspaceFolders || workspaceFolders.length === 0) { + return undefined; + } + + // Only one folder in workspace + if (workspaceFolders.length === 1) { + return workspaceFolders[0]; + } + + // Multiple folders in workspace - determine which is active based on active editor + const activeEditor = vscode.window.activeTextEditor; + if (activeEditor) { + const activeDocUri = activeEditor.document.uri; + const activeFolder = vscode.workspace.getWorkspaceFolder(activeDocUri); + if (activeFolder) { + return activeFolder; + } + } + + // Fallback to first workspace folder if no active editor or if active file + // isn't within any workspace folder + return workspaceFolders[0]; +} + +export function run(command: string, { cwd }: { cwd?: string } = {}) { + return new Promise<{ stdout: string; stderr: string; error: Error | null }>( + (resolve) => { + exec(command, { cwd, encoding: 'utf-8' }, (error, stdout, stderr) => { + resolve({ + stdout, + stderr, + error, + }); + }); + }, + ); +} From 6b812c901512717da7a869a70b6eb855ecb5656c Mon Sep 17 00:00:00 2001 From: Yassine El Khattabi Date: Fri, 4 Apr 2025 15:39:55 +0000 Subject: [PATCH 03/32] Initial working prototype --- package-lock.json | 401 +++++++++++++++++++++++++++++++++++-- package.json | 20 +- src/chat.ts | 58 ++++++ src/cli.ts | 302 +++++++++++++++++++--------- src/commands/chat.ts | 39 ++++ src/commands/index.ts | 36 ++++ src/commands/initialize.ts | 102 ++++++++++ src/extension.ts | 365 +-------------------------------- src/simple.ts | 244 ---------------------- src/testTree.ts | 120 ----------- src/tests.ts | 149 ++++++++++++++ src/utils.ts | 267 +++++++++++++++++++++--- tsconfig.json | 4 +- 13 files changed, 1240 insertions(+), 867 deletions(-) create mode 100644 src/chat.ts create mode 100644 src/commands/chat.ts create mode 100644 src/commands/index.ts create mode 100644 src/commands/initialize.ts delete mode 100644 src/simple.ts delete mode 100644 src/testTree.ts create mode 100644 src/tests.ts diff --git a/package-lock.json b/package-lock.json index 6cfafcd..dbc2d2f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,13 +11,15 @@ "@vscode/chat-extension-utils": "^0.0.0-alpha.1", "@vscode/prompt-tsx": "^0.3.0-alpha.12", "markdown-parser": "^0.0.8", + "node-ipc": "^12.0.0", "vscode": "^1.1.37", "ws": "^8.18.1" }, "devDependencies": { "@eslint/js": "^9.13.0", "@stylistic/eslint-plugin": "^2.9.0", - "@types/node": "^20", + "@types/node": "^20.17.30", + "@types/node-ipc": "^9.2.3", "@types/vscode": "^1.95.0", "@types/ws": "^8.18.0", "eslint": "^9.13.0", @@ -299,12 +301,23 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz", - "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==", + "version": "20.17.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.30.tgz", + "integrity": "sha512-7zf4YyHA+jvBNfVrk2Gtvs6x7E8V+YDW05bNfG2XkWDJfYRXrTiP/DsB2zSYTaHX0bGIujTBQdMVAhb+j7mwpg==", "dev": true, + "license": "MIT", "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/node-ipc": { + "version": "9.2.3", + "resolved": "https://registry.npmjs.org/@types/node-ipc/-/node-ipc-9.2.3.tgz", + "integrity": "sha512-/MvSiF71fYf3+zwqkh/zkVkZj1hl1Uobre9EMFy08mqfJNAmpR0vmPgOUdEIDVgifxHj6G1vYMPLSBLLxoDACQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" } }, "node_modules/@types/vscode": { @@ -609,11 +622,19 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "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, "dependencies": { "color-convert": "^2.0.1" }, @@ -697,11 +718,21 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, "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, "dependencies": { "color-name": "~1.1.4" }, @@ -712,8 +743,7 @@ "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 + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/commander": { "version": "2.15.1", @@ -727,6 +757,43 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, + "node_modules/copyfiles": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/copyfiles/-/copyfiles-2.4.1.tgz", + "integrity": "sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==", + "license": "MIT", + "dependencies": { + "glob": "^7.0.5", + "minimatch": "^3.0.3", + "mkdirp": "^1.0.4", + "noms": "0.0.0", + "through2": "^2.0.1", + "untildify": "^4.0.0", + "yargs": "^16.1.0" + }, + "bin": { + "copyfiles": "copyfiles", + "copyup": "copyfiles" + } + }, + "node_modules/copyfiles/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -773,6 +840,21 @@ "node": ">=0.3.1" } }, + "node_modules/easy-stack": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/easy-stack/-/easy-stack-1.0.1.tgz", + "integrity": "sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, "node_modules/es6-promise": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", @@ -788,6 +870,15 @@ "es6-promise": "^4.0.3" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -987,6 +1078,28 @@ "node": ">=0.10.0" } }, + "node_modules/event-pubsub": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/event-pubsub/-/event-pubsub-5.0.3.tgz", + "integrity": "sha512-2QiHxshejKgJrYMzSI9MEHrvhmzxBL+eLyiM5IiyjDBySkgwS2+tdtnO3gbx8pEisu/yOFCIhfCb63gCEu0yBQ==", + "license": "MIT", + "dependencies": { + "copyfiles": "^2.4.0", + "strong-type": "^0.1.3" + }, + "engines": { + "node": ">=13.0.0" + } + }, + "node_modules/event-pubsub/node_modules/strong-type": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/strong-type/-/strong-type-0.1.6.tgz", + "integrity": "sha512-eJe5caH6Pi5oMMeQtIoBPpvNu/s4jiyb63u5tkHNnQXomK+puyQ5i+Z5iTLBr/xUz/pIcps0NSfzzFI34+gAXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1113,6 +1226,15 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "license": "ISC" }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -1281,6 +1403,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -1303,12 +1434,39 @@ "node": ">=0.12.0" } }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/js-message": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/js-message/-/js-message-1.0.7.tgz", + "integrity": "sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA==", + "license": "MIT", + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/js-queue": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/js-queue/-/js-queue-2.0.2.tgz", + "integrity": "sha512-pbKLsbCfi7kriM3s1J4DDCo7jQkI58zPLHi0heXPzPlj0hjUsm+FesPUbE0DSbIVIK503A36aUBoCN7eMFedkA==", + "license": "MIT", + "dependencies": { + "easy-stack": "^1.0.1" + }, + "engines": { + "node": ">=1.0.0" + } + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -1560,6 +1718,31 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/node-ipc": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/node-ipc/-/node-ipc-12.0.0.tgz", + "integrity": "sha512-QHJ2gAJiqA3cM7cQiRjLsfCOBRB0TwQ6axYD4FSllQWipEbP6i7Se1dP8EzPKk5J1nCe27W69eqPmCoKyQ61Vg==", + "license": "MIT", + "dependencies": { + "event-pubsub": "5.0.3", + "js-message": "1.0.7", + "js-queue": "2.0.2", + "strong-type": "^1.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/noms": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz", + "integrity": "sha512-lNDU9VJaOPxUmXcLb+HQFeUgQQPtMI24Gt6hgfuMHRJgMRHMF/qZ4HJD3GDru4sSw9IQl2jPjAYnQrdIeLbwow==", + "license": "ISC", + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "~1.0.31" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1694,6 +1877,12 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -1725,6 +1914,27 @@ ], "license": "MIT" }, + "node_modules/readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -1770,6 +1980,12 @@ "queue-microtask": "^1.2.2" } }, + "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==", + "license": "MIT" + }, "node_modules/semver": { "version": "7.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", @@ -1823,6 +2039,38 @@ "source-map": "^0.6.0" } }, + "node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "license": "MIT" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -1836,6 +2084,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strong-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strong-type/-/strong-type-1.1.0.tgz", + "integrity": "sha512-X5Z6riticuH5GnhUyzijfDi1SoXas8ODDyN7K8lJeQK+Jfi4dKdoJGL4CXTskY/ATBcN+rz5lROGn1tAUkOX7g==", + "license": "MIT", + "engines": { + "node": ">=12.21.0" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -1855,6 +2112,46 @@ "dev": true, "license": "MIT" }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/through2/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/through2/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==", + "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/through2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -1931,10 +2228,20 @@ } }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "license": "MIT", + "engines": { + "node": ">=8" + } }, "node_modules/uri-js": { "version": "4.4.1", @@ -1946,6 +2253,12 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/vscode": { "version": "1.1.37", "resolved": "https://registry.npmjs.org/vscode/-/vscode-1.1.37.tgz", @@ -2059,6 +2372,23 @@ "node": ">= 8" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -2086,6 +2416,51 @@ } } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index b056c0a..d5512e0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "testdriver", - "publisher": "vscode-samples", + "publisher": "testdriverai", "displayName": "TestDriver VS Code Extension", "description": "AI QA Agents", "repository": { @@ -157,37 +157,39 @@ "commands": [ { "command": "testdriver.init", - "title": "Initialize testdriver in the active workspace" + "title": "TestDriver: Initialize testdriver in the active workspace" }, { - "command": "dry", - "title": "Analyze screen and generate test steps, but don't run them." + "command": "testdriver.dry", + "title": "TestDriver: Analyze screen and generate test steps, but don't run them." }, { - "command": "try", - "title": "Analyze screen and generate test steps, then run." + "command": "testdriver.try", + "title": "TestDriver: Analyze screen and generate test steps, then run them." } ] }, "main": "./out/extension.js", "scripts": { "vscode:prepublish": "npm run compile", - "compile": "tsc -p ./", + "compile": "tsc", "format": "prettier --write .", "lint": "eslint", - "watch": "tsc -watch -p ./" + "watch": "tsc -watch" }, "dependencies": { "@vscode/chat-extension-utils": "^0.0.0-alpha.1", "@vscode/prompt-tsx": "^0.3.0-alpha.12", "markdown-parser": "^0.0.8", + "node-ipc": "^12.0.0", "vscode": "^1.1.37", "ws": "^8.18.1" }, "devDependencies": { "@eslint/js": "^9.13.0", "@stylistic/eslint-plugin": "^2.9.0", - "@types/node": "^20", + "@types/node": "^20.17.30", + "@types/node-ipc": "^9.2.3", "@types/vscode": "^1.95.0", "@types/ws": "^8.18.0", "eslint": "^9.13.0", diff --git a/src/chat.ts b/src/chat.ts new file mode 100644 index 0000000..c9b641a --- /dev/null +++ b/src/chat.ts @@ -0,0 +1,58 @@ +import * as vscode from 'vscode'; +import { TDInstance } from './cli'; +import { getActiveWorkspaceFolder } from './utils'; + +export const PARTICIPANT_ID = 'testdriver.driver'; + +export function registerChatParticipant(_context: vscode.ExtensionContext) { + vscode.chat.createChatParticipant(PARTICIPANT_ID, handler); +} + +const handler: vscode.ChatRequestHandler = async ( + request: vscode.ChatRequest, + _context: vscode.ChatContext, + stream: vscode.ChatResponseStream, + token: vscode.CancellationToken, +): Promise => { + if (request.command) { + const commands = ['dry', 'try']; + if (commands.includes(request.command)) { + const workspace = getActiveWorkspaceFolder(); + if (!workspace) { + stream.progress('No workspace found'); + return; + } + stream.progress('Looking at screen...'); + + const abortController = new AbortController(); + token.onCancellationRequested(() => abortController.abort()); + + const file = `testdriver/testdriver_${Date.now()}.yaml`; + const instance = new TDInstance(workspace, file); + + await instance.run(`/${request.command} ${request.prompt}`, { + signal: abortController.signal, + callback: (event) => { + if (typeof event === 'string') { + stream.markdown(event); + } else { + stream.markdown( + `\n\n\`\`\`${event.type ?? ''}\n${event.content}\n\`\`\`\n\n`, + ); + if (['yaml', 'yml'].includes(event.type?.toLowerCase() ?? '')) { + stream.button({ + command: 'testdriver.codeblock.run', + title: vscode.l10n.t('Run Steps'), + arguments: [file, workspace], // Send the YML code as an argument + }); + } + } + }, + }); + instance.destroy(); + } else { + stream.progress('Unsupported command: ' + request.command); + } + return; + } +}; diff --git a/src/cli.ts b/src/cli.ts index d4a2a06..45a6149 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,114 +1,230 @@ -import { homedir } from 'os'; -import path from 'path'; -import WebSocket from 'ws'; // Add WebSocket import +import nodeIPC from 'node-ipc'; import * as vscode from 'vscode'; - -interface CallTDCLIResult { - yml: string; +import EventEmitter from 'node:events'; +import { ChildProcess, spawn } from 'node:child_process'; +import { MarkdownStreamParser, MarkdownParserEvent } from './utils'; + +type InferType = T extends new () => infer U ? U : undefined; +type IPCType = InferType; +const { IPC } = nodeIPC; + +interface EventsMap { + stdout: [string]; + stderr: [string]; + exit: [number | null]; + output: [string]; + pending: []; + idle: []; + busy: []; } -const tempFile = `testdriver/testdriver-${new Date().getTime()}.yaml`; +const MAX_RETRIES = 10; +export class TDInstance extends EventEmitter { + state: 'pending' | 'idle' | 'busy' | 'exit'; + private client: IPCType; + private process: ChildProcess; + private serverId: string; + private cleanup?: () => void; + private isLocked = false; + + constructor( + public workspace: vscode.WorkspaceFolder, + public file?: string, + ) { + super(); + this.state = 'pending'; + + this.client = new IPC(); + this.client.config.id = `testdriverai_${this.workspace.name}_${process.pid}`; + this.client.config.retry = 50; + this.client.config.maxRetries = MAX_RETRIES; + this.client.config.silent = true; + + const args: string[] = []; + if (file) { + args.push(file); + } + this.process = spawn(`testdriverai`, args, { + env: process.env, + cwd: workspace.uri.fsPath, + stdio: 'pipe', + }); -interface WebSocketMessage { - event: string; - message?: string; -} + this.serverId = `testdriverai_${this.process.pid}`; -// Connect to WebSocket server -const terminal = vscode.window.createTerminal('TestDriver'); -terminal.show(); -terminal.sendText( - `/Users/ianjennings/Development/testdriverai/index.js ${tempFile}`, -); - -const callTDCLI = function ( - command: string, - stream: vscode.ChatResponseStream, -): Promise { - const ws = new WebSocket('ws://localhost:8080'); - - return new Promise((resolve, reject) => { - ws.on('open', () => { - console.log('WebSocket connection opened'); - ws.send( - JSON.stringify({ - event: 'input', - data: command, - }), - ); - }); + this.on('pending', () => (this.state = 'pending')) + .on('idle', () => (this.state = 'idle')) + .on('busy', () => (this.state = 'busy')) + .on('exit', () => { + this.state = 'exit'; + this.destroy(); + }); - ws.on('close', () => { - console.log('WebSocket connection closed'); - }); + this.process.stdout!.on('data', (data) => + this.emit('stdout', data.toString()), + ); + this.process.stderr!.on('data', (data) => + this.emit('stderr', data.toString()), + ); - ws.on('error', (error: Error) => { - console.error(`WebSocket error: ${error}`); - console.log(error); - reject(error); + this.process.once('error', () => { + this.emit('exit', 1); }); - let buff = ''; - let insideYML = false; - let YMLever = false; - let hasBlock = false; + this.process.once('exit', (code) => { + this.emit('exit', code); + }); - ws.on('message', (data: string) => { - let parsedData: WebSocketMessage = JSON.parse(data); + this.process.once('spawn', () => { + let retryCount = 0; + this.client.connectTo(this.serverId); + + const onConnect = () => { + retryCount = 0; + this.emit('pending'); + }; + const onError = () => { + retryCount++; + if (this.state === 'pending' && retryCount <= MAX_RETRIES) { + return; + } + this.emit('exit', 1); + }; - if (parsedData.event === 'output' && parsedData.message) { - let nextmsg = parsedData.message; + const onDisconnect = () => { + if (this.state !== 'pending') { + this.emit('exit', null); + } + }; + + const handleMessage = (message: { event: string; data: unknown }) => { + const { event, data } = message; + switch (event) { + case 'interactive': + this.emit((data as boolean) ? 'idle' : 'busy'); + break; + case 'output': + this.emit('output', data as string); + } + }; + + this.client.of[this.serverId] + .on('connect', onConnect) + .on('error', onError) + .on('disconnect', onDisconnect) + .on('message', handleMessage); + + this.cleanup = () => { + this.client.of[this.serverId] + ?.off('connect', onConnect) + ?.off('error', onError) + ?.off('disconnect', onDisconnect) + ?.off('message', handleMessage); + }; + }); - for (const char of parsedData.message) { - buff += char; - if (buff.slice(-3) === '```') { - console.log('yml detected'); + // Debug + this.on('pending', () => console.log('[debug:pending]')) + .on('idle', () => console.log('[debug:idle]')) + .on('busy', () => console.log('[debug:busy]')) + .on('exit', (code) => console.log('[debug:exit]', code)) + // .on('stdout', (data) => process.stdout.write(data)) + // .on('stderr', (data) => process.stderr.write(data)) + .on('output', (data) => process.stdout.write(data)); + } + + async run( + command: string, + options: { + signal?: AbortSignal; + callback?: (event: MarkdownParserEvent) => void; + } = {}, + ) { + if (this.isLocked) { + throw new Error('A command is already running'); + } + this.isLocked = true; + const signal = options.signal ?? new AbortController().signal; + + await new Promise((resolve, reject) => { + console.log('Waiting for cli to become available'); + this.once('idle', () => { + console.log('cli is available'); + resolve(true); + }); + this.once('exit', () => reject(new Error('Process exited'))); + signal.onabort = () => { + reject(new Error('Command aborted')); + }; + }).catch((err) => { + this.isLocked = false; + throw err; + }); - insideYML = !insideYML; + return new Promise<{ fullOutput: string; events: MarkdownParserEvent[] }>( + (resolve, reject) => { + signal.onabort = () => { + reject(new Error('Command aborted')); + }; + const result: { fullOutput: string; events: MarkdownParserEvent[] } = { + fullOutput: '', + events: [], + }; + + this.once('exit', (code) => + code === 0 ? resolve(result) : reject(new Error('Process exited')), + ); + const parser = new MarkdownStreamParser(); - if (insideYML) { - console.log('pushing'); - nextmsg = nextmsg + ''; - console.log(nextmsg); - YMLever = true; - } + const handleOutput = (message: string) => { + result.fullOutput += message; + for (const char of message) { + parser.processChar(char); } - } + }; - console.log(buff); - console.log('--------'); - console.log(nextmsg); - console.log('-------------------'); - stream.markdown(nextmsg); - - if (!insideYML && YMLever) { - // Render a button to trigger a VS Code command - stream.button({ - command: 'testdriver.codeblock.run', - title: vscode.l10n.t('Run Steps'), - arguments: [tempFile], // Send the YML code as an argument - }); + parser.on('markdown', (event) => { + result.events.push(event); + options.callback?.(event); + }); - YMLever = false; // Reset YMLever to handle multiple code blocks - } - } + parser.on('codeblock', (event) => { + result.events.push(event); + options.callback?.(event); + }); - if (parsedData.event === 'done') { - resolve({ - yml: null, + this.once('busy', () => { + this.on('output', handleOutput); + this.once('idle', () => { + this.off('output', handleOutput); + parser.end(); + resolve(result); + }); }); - } - ws.on('close', () => { - console.log('WebSocket connection closed'); - reject( - new Error('WebSocket connection closed before receiving done event'), - ); + this.client.of[this.serverId].emit('message', { + event: 'input', + data: command, + }); + }, + ) + .then((result) => { + this.isLocked = false; + return result; + }) + .catch((err) => { + this.isLocked = false; + this.emit('exit', 1); + throw err; }); - }); - }); -}; - -export default { - exec: callTDCLI, -}; + } + + destroy() { + this.process.stdout?.removeAllListeners(); + this.process.stderr?.removeAllListeners(); + this.cleanup?.(); + this.client.disconnect(this.serverId); + this.process.kill(9); + this.state = 'exit'; + } +} diff --git a/src/commands/chat.ts b/src/commands/chat.ts new file mode 100644 index 0000000..5668924 --- /dev/null +++ b/src/commands/chat.ts @@ -0,0 +1,39 @@ +import * as vscode from 'vscode'; +import { PARTICIPANT_ID } from '../chat'; + +const getUserPrompt = async () => { + const userInput = await vscode.window.showInputBox({ + value: '', + prompt: 'Prompt for TestDriver', + placeHolder: 'Your prompt ...', + validateInput: (text) => { + return text.trim().length > 0 ? null : 'Prompt cannot be empty'; + }, + }); + return userInput?.trim(); +}; + +export const handleTDCommandInChat = async ( + testdriverCommand: 'dry' | 'try', + testdriverPrompt?: string, +) => { + const options = { + query: `@testdriver /${testdriverCommand} ${testdriverPrompt}`, + participant: PARTICIPANT_ID, + }; + + const chatVisible = vscode.window.tabGroups.all.some((group) => + group.tabs.some((tab) => tab.label.includes('Chat')), + ); + + const command = chatVisible + ? 'workbench.action.chat.new' + : 'workbench.action.chat.open'; + + await vscode.commands.executeCommand(`/${command}`, options); +}; + +export const testdriverCommand = (command: 'dry' | 'try') => async () => { + const prompt = await getUserPrompt(); + await handleTDCommandInChat(command, prompt); +}; diff --git a/src/commands/index.ts b/src/commands/index.ts new file mode 100644 index 0000000..2425a53 --- /dev/null +++ b/src/commands/index.ts @@ -0,0 +1,36 @@ +import * as vscode from 'vscode'; +import { initialize } from './initialize'; +import { testdriverCommand } from './chat'; + +const registerCtrlPCommands = () => { + const chatCommands = ['dry', 'try'] as const; + + for (const command of chatCommands) { + vscode.commands.registerCommand( + `testdriver.${command}`, + testdriverCommand(command), + ); + } + + vscode.commands.registerCommand('testdriver.init', initialize); +}; + +const registerOtherCommands = () => { + vscode.commands.registerCommand( + 'testdriver.codeblock.run', + async (yaml: string, workspace: vscode.WorkspaceFolder) => { + console.log('Running codeblock', yaml, 'workspace', workspace); + const terminal = vscode.window.createTerminal({ + name: 'TestDriver', + cwd: workspace.uri.fsPath, + }); + terminal.show(); + terminal.sendText(`testdriverai run ${yaml}`, true); + }, + ); +}; + +export const registerCommands = () => { + registerCtrlPCommands(); + registerOtherCommands(); +}; diff --git a/src/commands/initialize.ts b/src/commands/initialize.ts new file mode 100644 index 0000000..c4c810d --- /dev/null +++ b/src/commands/initialize.ts @@ -0,0 +1,102 @@ +import * as vscode from 'vscode'; +import { run } from '../utils'; + +export const initialize = async () => { + let workspaceFolder: vscode.WorkspaceFolder | undefined = undefined; + + if ( + !vscode.workspace.workspaceFolders || + vscode.workspace.workspaceFolders.length === 0 + ) { + await vscode.window.showErrorMessage('No workspace folder found'); + return; + } + + if (vscode.workspace.workspaceFolders.length === 1) { + workspaceFolder = vscode.workspace.workspaceFolders[0]; + } else { + await vscode.window.showWorkspaceFolderPick({ + placeHolder: 'Select a workspace folder', + }); + } + + let statusBar = vscode.window.setStatusBarMessage( + 'Initializing Testdriver...', + ); + + // Check workspace folder + statusBar.dispose(); + + if (!workspaceFolder) { + await vscode.window.showErrorMessage('No workspace folder found'); + return; + } + + const folder = workspaceFolder.uri.fsPath; + console.log('folder', folder); + + // Check if npm is installed + statusBar = vscode.window.setStatusBarMessage('Checking npm...'); + const { error: npmError, stdout: npmVersion } = await run('npm -v', { + cwd: folder, + }); + statusBar.dispose(); + console.log('npmVersion', npmVersion); + if (npmError) { + await vscode.window.showErrorMessage('npm is not installed'); + return; + } + + // Check if testdriverai is installed + statusBar = vscode.window.setStatusBarMessage('Checking testdriverai...'); + const { + error: tdError, + stdout: tdStdout, + stderr: tdStderr, + } = await run('testdriverai --help', { cwd: folder }); + statusBar.dispose(); + + console.log({ error: tdError, stdout: tdStdout, stderr: tdStderr }); + if (tdError) { + statusBar = vscode.window.setStatusBarMessage('Installing testdriverai...'); + const { error: installError } = await run( + 'npm install -g testdriverai@beta', + { cwd: folder }, + ); + statusBar.dispose(); + if (installError) { + await vscode.window.showErrorMessage('Failed to install testdriverai'); + return; + } + } + + // const testdriverDir = vscode.Uri.parse('testdriver'); + // const stat = await vscode.workspace.fs.stat(testdriverDir); + // console.log({ stat }); + // Check if Testdriver is already initialized + // if (stat) { + // await vscode.window.showInformationMessage( + // 'Skipping, Testdriver is already initialized in this workspace', + // ); + // return; + // } + + statusBar = vscode.window.setStatusBarMessage( + 'Initializing Testdriver project...', + ); + const terminal = vscode.window.createTerminal('Testdriver'); + const promise = new Promise((resolve) => { + const disposable = vscode.window.onDidEndTerminalShellExecution((event) => { + if (event.terminal === terminal) { + disposable.dispose(); + resolve(); + } + }); + }); + terminal.sendText('testdriverai init'); + terminal.show(); + + await promise.finally(() => { + statusBar.dispose(); + }); +}; diff --git a/src/extension.ts b/src/extension.ts index 57befaa..f06d280 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,364 +1,13 @@ import * as vscode from 'vscode'; -import { registerSimpleParticipant } from './simple'; - +import { registerChatParticipant } from './chat'; +import { registerCommands } from './commands'; +import { setupTests } from './tests'; export function deactivate() {} -import { - getContentFromFilesystem, - TestCase, - testData, - TestFile, -} from './testTree'; - export async function activate(context: vscode.ExtensionContext) { - registerSimpleParticipant(context); - - const ctrl = vscode.tests.createTestController( - 'mathTestController', - 'Markdown Math', - ); - context.subscriptions.push(ctrl); - - const fileChangedEmitter = new vscode.EventEmitter(); - const watchingTests = new Map< - vscode.TestItem | 'ALL', - vscode.TestRunProfile | undefined - >(); - fileChangedEmitter.event((uri) => { - if (watchingTests.has('ALL')) { - startTestRun( - new vscode.TestRunRequest( - undefined, - undefined, - watchingTests.get('ALL'), - true, - ), - ); - return; - } - - const include: vscode.TestItem[] = []; - let profile: vscode.TestRunProfile | undefined; - for (const [item, thisProfile] of watchingTests) { - const cast = item as vscode.TestItem; - if (cast.uri?.toString() == uri.toString()) { - include.push(cast); - profile = thisProfile; - } - } - - if (include.length) { - startTestRun( - new vscode.TestRunRequest(include, undefined, profile, true), - ); - } - }); - - async function runHandler( - shouldDebug: boolean, - request: vscode.TestRunRequest, - token: vscode.CancellationToken, - ) { - const run = controller.createTestRun(request); - const queue: vscode.TestItem[] = []; - - // Loop through all included tests, or all known tests, and add them to our queue - if (request.include) { - request.include.forEach((test) => queue.push(test)); - } else { - controller.items.forEach((test) => queue.push(test)); - } - - // For every test that was queued, try to run it. Call run.passed() or run.failed(). - // The `TestMessage` can contain extra information, like a failing location or - // a diff output. But here we'll just give it a textual message. - while (queue.length > 0 && !token.isCancellationRequested) { - const test = queue.pop()!; - - // Skip tests the user asked to exclude - if (request.exclude?.includes(test)) { - continue; - } - - switch (getType(test)) { - case ItemType.File: - // If we're running a file and don't know what it contains yet, parse it now - if (test.children.size === 0) { - await parseTestsInFileContents(test); - } - break; - case ItemType.TestCase: - // Otherwise, just run the test case. Note that we don't need to manually - // set the state of parent tests; they'll be set automatically. - const start = Date.now(); - try { - await assertTestPasses(test); - run.passed(test, Date.now() - start); - } catch (e) { - run.failed( - test, - new vscode.TestMessage(e.message), - Date.now() - start, - ); - } - break; - } - - test.children.forEach((test) => queue.push(test)); - } - - // Make sure to end the run after all tests have been executed: - run.end(); - } - - const startTestRun = (request: vscode.TestRunRequest) => { - const queue: { test: vscode.TestItem; data: TestCase }[] = []; - const run = ctrl.createTestRun(request); - // map of file uris to statements on each line: - const coveredLines = new Map< - /* file uri */ string, - (vscode.StatementCoverage | undefined)[] - >(); - - const discoverTests = async (tests: Iterable) => { - for (const test of tests) { - if (request.exclude?.includes(test)) { - continue; - } - - const data = testData.get(test); - if (data instanceof TestCase) { - run.enqueued(test); - queue.push({ test, data }); - } else { - if (data instanceof TestFile && !data.didResolve) { - await data.updateFromDisk(ctrl, test); - } - - await discoverTests(gatherTestItems(test.children)); - } - - if ( - test.uri && - !coveredLines.has(test.uri.toString()) && - request.profile?.kind === vscode.TestRunProfileKind.Coverage - ) { - try { - const lines = (await getContentFromFilesystem(test.uri)).split( - '\n', - ); - coveredLines.set( - test.uri.toString(), - lines.map((lineText, lineNo) => - lineText.trim().length - ? new vscode.StatementCoverage( - 0, - new vscode.Position(lineNo, 0), - ) - : undefined, - ), - ); - } catch { - // ignored - } - } - } - }; - - const runTestQueue = async () => { - for (const { test, data } of queue) { - run.appendOutput(`Running ${test.id}\r\n`); - - if (run.token.isCancellationRequested) { - run.skipped(test); - } else { - run.started(test); - await data.run(test, run); - } - - const lineNo = test.range!.start.line; - const fileCoverage = coveredLines.get(test.uri!.toString()); - const lineInfo = fileCoverage?.[lineNo]; - - if (lineInfo) { - (lineInfo.executed as number)++; - } - - run.appendOutput(`Completed ${test.id}\r\n`); - } - - for (const [uri, statements] of coveredLines) { - run.addCoverage(new MarkdownFileCoverage(uri, statements)); - } - - run.end(); - }; - - discoverTests(request.include ?? gatherTestItems(ctrl.items)).then( - runTestQueue, - ); - }; - - ctrl.refreshHandler = async () => { - await Promise.all( - getWorkspaceTestPatterns().map(({ pattern }) => - findInitialFiles(ctrl, pattern), - ), - ); - }; - - ctrl.createRunProfile( - 'Run Tests', - vscode.TestRunProfileKind.Run, - runHandler, - true, - undefined, - true, - ); - - const coverageProfile = ctrl.createRunProfile( - 'Run with Coverage', - vscode.TestRunProfileKind.Coverage, - runHandler, - true, - undefined, - true, - ); - coverageProfile.loadDetailedCoverage = async (_testRun, coverage) => { - if (coverage instanceof MarkdownFileCoverage) { - return coverage.coveredLines.filter( - (l): l is vscode.StatementCoverage => !!l, - ); - } - - return []; - }; - - ctrl.resolveHandler = async (item) => { - if (!item) { - context.subscriptions.push( - ...startWatchingWorkspace(ctrl, fileChangedEmitter), - ); - return; - } - - const data = testData.get(item); - if (data instanceof TestFile) { - await data.updateFromDisk(ctrl, item); - } - }; - - function updateNodeForDocument(e: vscode.TextDocument) { - if (e.uri.scheme !== 'file') { - return; - } - - if (!e.uri.path.endsWith('.yaml')) { - return; - } - - const { file, data } = getOrCreateFile(ctrl, e.uri); - data.updateFromContents(ctrl, e.getText(), file); - } - - for (const document of vscode.workspace.textDocuments) { - updateNodeForDocument(document); - } - - context.subscriptions.push( - vscode.workspace.onDidOpenTextDocument(updateNodeForDocument), - vscode.workspace.onDidChangeTextDocument((e) => - updateNodeForDocument(e.document), - ), - ); -} - -function getOrCreateFile(controller: vscode.TestController, uri: vscode.Uri) { - const existing = controller.items.get(uri.toString()); - if (existing) { - return { file: existing, data: testData.get(existing) as TestFile }; - } - - const file = controller.createTestItem( - uri.toString(), - uri.path.split('/').pop()!, - uri, - ); - controller.items.add(file); - - const data = new TestFile(); - testData.set(file, data); - - file.canResolveChildren = true; - return { file, data }; -} - -function gatherTestItems(collection: vscode.TestItemCollection) { - const items: vscode.TestItem[] = []; - collection.forEach((item) => items.push(item)); - return items; -} - -function getWorkspaceTestPatterns() { - if (!vscode.workspace.workspaceFolders) { - return []; - } - - return vscode.workspace.workspaceFolders.map((workspaceFolder) => ({ - workspaceFolder, - pattern: new vscode.RelativePattern( - workspaceFolder, - 'testdriver/**/*.yaml', - ), - })); -} - -async function findInitialFiles( - controller: vscode.TestController, - pattern: vscode.GlobPattern, -) { - for (const file of await vscode.workspace.findFiles(pattern)) { - getOrCreateFile(controller, file); - } -} - -function startWatchingWorkspace( - controller: vscode.TestController, - fileChangedEmitter: vscode.EventEmitter, -) { - return getWorkspaceTestPatterns().map(({ pattern }) => { - const watcher = vscode.workspace.createFileSystemWatcher(pattern); - - watcher.onDidCreate((uri) => { - getOrCreateFile(controller, uri); - fileChangedEmitter.fire(uri); - }); - watcher.onDidChange(async (uri) => { - const { file, data } = getOrCreateFile(controller, uri); - if (data.didResolve) { - await data.updateFromDisk(controller, file); - } - fileChangedEmitter.fire(uri); - }); - watcher.onDidDelete((uri) => controller.items.delete(uri.toString())); - - findInitialFiles(controller, pattern); - - return watcher; - }); -} + registerCommands(); + registerChatParticipant(context); + const controller = setupTests(); -class MarkdownFileCoverage extends vscode.FileCoverage { - constructor( - uri: string, - public readonly coveredLines: (vscode.StatementCoverage | undefined)[], - ) { - super(vscode.Uri.parse(uri), new vscode.TestCoverageCount(0, 0)); - for (const line of coveredLines) { - if (line) { - this.statementCoverage.covered += line.executed ? 1 : 0; - this.statementCoverage.total++; - } - } - } + context.subscriptions.push(controller); } diff --git a/src/simple.ts b/src/simple.ts deleted file mode 100644 index cae597f..0000000 --- a/src/simple.ts +++ /dev/null @@ -1,244 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import * as vscode from 'vscode'; -import cli from './cli'; -const PARTICIPANT_ID = 'testdriver.driver'; -import spec from './spec'; -import { getActiveWorkspaceFolder, run } from './utils'; - -vscode.commands.registerCommand('testdriver.codeblock.run', async (yaml) => { - vscode.window.showInformationMessage(`Running YML steps:`); - await cli.exec(`/yaml ${yaml}`); -}); - -vscode.commands.registerCommand('testdriver.init', async () => { - let statusBar = vscode.window.setStatusBarMessage( - 'Initializing Testdriver...', - ); - - // Check workspace folder - const workspaceFolder = getActiveWorkspaceFolder(); - statusBar.dispose(); - - if (!workspaceFolder) { - await vscode.window.showErrorMessage('No workspace folder found'); - return; - } - - const folder = workspaceFolder.uri.fsPath; - console.log('folder', folder); - - // Check if npm is installed - statusBar = vscode.window.setStatusBarMessage('Checking npm...'); - const { error: npmError, stdout: npmVersion } = await run('npm -v', { - cwd: folder, - }); - statusBar.dispose(); - console.log('npmVersion', npmVersion); - if (npmError) { - await vscode.window.showErrorMessage('npm is not installed'); - return; - } - - // Check if testdriverai is installed - statusBar = vscode.window.setStatusBarMessage('Checking testdriverai...'); - const { - error: tdError, - stdout: tdStdout, - stderr: tdStderr, - } = await run('testdriverai --help', { cwd: folder }); - statusBar.dispose(); - - console.log({ error: tdError, stdout: tdStdout, stderr: tdStderr }); - if (tdError) { - statusBar = vscode.window.setStatusBarMessage('Installing testdriverai...'); - const { error: installError } = await run( - 'npm install -g testdriverai@beta', - { cwd: folder }, - ); - statusBar.dispose(); - if (installError) { - await vscode.window.showErrorMessage('Failed to install testdriverai'); - return; - } - } - - // Check if Testdriver is already initialized - if (fs.existsSync(path.join(folder, 'testdriver'))) { - await vscode.window.showInformationMessage( - 'Skipping, Testdriver is already initialized in this workspace', - ); - return; - } - - statusBar = vscode.window.setStatusBarMessage( - 'Initializing Testdriver project...', - ); - const terminal = vscode.window.createTerminal('Testdriver'); - const promise = new Promise((resolve) => { - const disposable = vscode.window.onDidEndTerminalShellExecution((event) => { - if (event.terminal === terminal) { - disposable.dispose(); - resolve(); - } - }); - }); - terminal.sendText('testdriverai init'); - terminal.show(); - - await promise.finally(() => { - statusBar.dispose(); - }); -}); - -interface ICatChatResult extends vscode.ChatResult { - metadata: { - command: string; - }; -} -// const ansiRegex = (({ onlyFirst = false } = {}) => { -// const pattern = [ -// "[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)", -// "(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))", -// ].join("|"); -// return new RegExp(pattern, onlyFirst ? undefined : "g"); -// })(); - -// function stripAnsi(string) { -// if (typeof string !== "string") { -// throw new TypeError(`Expected a \`string\`, got \`${typeof string}\``); -// } - -// return string.replace(ansiRegex, ""); -// } - -export function registerSimpleParticipant(context: vscode.ExtensionContext) { - // Define a Cat chat handler. - const handler: vscode.ChatRequestHandler = async ( - request: vscode.ChatRequest, - context: vscode.ChatContext, - stream: vscode.ChatResponseStream, - token: vscode.CancellationToken, - ): Promise => { - // generate a temporary file - - if (request.command === 'dry') { - stream.progress('Looking at screen...'); - await cli.exec('/dry ' + request.prompt, stream); - } else if (request.command === 'try') { - stream.progress('Looking at screen...'); - await cli.exec(request.prompt, stream); - } else { - stream.progress('Staring my engine...'); - - console.log(context.history); - - try { - const messages = [vscode.LanguageModelChatMessage.User(spec)]; - - // get all the previous participant messages - const previousMessages = context.history.filter( - (h) => h instanceof vscode.ChatResponseTurn, - ); - - // add the previous messages to the messages array - previousMessages.forEach((m) => { - let fullMessage = ''; - m.response.forEach((r) => { - const mdPart = r as vscode.ChatResponseMarkdownPart; - fullMessage += mdPart.value.value; - }); - messages.push(vscode.LanguageModelChatMessage.Assistant(fullMessage)); - }); - - // add in the user's message - messages.push(vscode.LanguageModelChatMessage.User(request.prompt)); - - // send the request - const chatResponse = await request.model.sendRequest( - messages, - {}, - token, - ); - - for await (const fragment of chatResponse.text) { - stream.markdown(fragment); - } - } catch (err) { - console.log('err', err); - handleError(logger, err, stream); - } - } - }; - - // Chat participants appear as top-level options in the chat input - // when you type `@`, and can contribute sub-commands in the chat input - // that appear when you type `/`. - const td = vscode.chat.createChatParticipant(PARTICIPANT_ID, handler); - td.iconPath = vscode.Uri.joinPath(context.extensionUri, 'icon.png'); - td.followupProvider = { - provideFollowups( - _result: ICatChatResult, - _context: vscode.ChatContext, - _token: vscode.CancellationToken, - ) { - return [ - { - prompt: 'let us play', - label: vscode.l10n.t('Run this step'), - command: 'play', - } satisfies vscode.ChatFollowup, - ]; - }, - }; - - const logger = vscode.env.createTelemetryLogger({ - sendEventData(eventName, data) { - // Capture event telemetry - console.log(`Event: ${eventName}`); - console.log(`Data: ${JSON.stringify(data)}`); - }, - sendErrorData(error, data) { - // Capture error telemetry - console.error(`Error: ${error}`); - console.error(`Data: ${JSON.stringify(data)}`); - }, - }); - - context.subscriptions.push( - td.onDidReceiveFeedback((feedback: vscode.ChatResultFeedback) => { - // Log chat result feedback to be able to compute the success matric of the participant - // unhelpful / totalRequests is a good success metric - logger.logUsage('chatResultFeedback', { - kind: feedback.kind, - }); - }), - ); -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function handleError( - logger: vscode.TelemetryLogger, - err: any, - stream: vscode.ChatResponseStream, -): void { - // making the chat request might fail because - // - model does not exist - // - user consent not given - // - quote limits exceeded - logger.logError(err); - - if (err instanceof vscode.LanguageModelError) { - console.log(err.message, err.code, err.cause); - if (err.cause instanceof Error && err.cause.message.includes('off_topic')) { - stream.markdown( - vscode.l10n.t( - "I'm sorry, I can only explain computer science concepts.", - ), - ); - } - } else { - // re-throw other errors so they show up in the UI - throw err; - } -} diff --git a/src/testTree.ts b/src/testTree.ts deleted file mode 100644 index 7a8a099..0000000 --- a/src/testTree.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { TextDecoder } from 'util'; -import * as vscode from 'vscode'; -import { parseMarkdown } from './parser'; -import cli from './cli'; - -const textDecoder = new TextDecoder('utf-8'); - -export type MarkdownTestData = TestFile | TestHeading | TestCase; - -export const testData = new WeakMap(); - -let generationCounter = 0; - -export const getContentFromFilesystem = async (uri: vscode.Uri) => { - try { - const rawContent = await vscode.workspace.fs.readFile(uri); - return textDecoder.decode(rawContent); - } catch (e) { - console.warn(`Error providing tests for ${uri.fsPath}`, e); - return ''; - } -}; - -export class TestFile { - public didResolve = false; - - public async updateFromDisk( - controller: vscode.TestController, - item: vscode.TestItem, - ) { - try { - const content = await getContentFromFilesystem(item.uri!); - item.error = undefined; - this.updateFromContents(controller, content, item); - } catch (e) { - item.error = (e as Error).stack; - } - } - - /** - * Parses the tests from the input text, and updates the tests contained - * by this file to be those from the text, - */ - public updateFromContents( - controller: vscode.TestController, - content: string, - item: vscode.TestItem, - ) { - const ancestors = [{ item, children: [] as vscode.TestItem[] }]; - const thisGeneration = generationCounter++; - this.didResolve = true; - - const ascend = (depth: number) => { - while (ancestors.length > depth) { - const finished = ancestors.pop()!; - finished.item.children.replace(finished.children); - } - }; - - ascend(0); // finish and assign children for all remaining items - } -} - -export class TestHeading { - constructor(public generation: number) {} -} - -type Operator = '+' | '-' | '*' | '/'; - -export class TestCase { - constructor( - private readonly a: number, - private readonly operator: Operator, - private readonly b: number, - private readonly expected: number, - public generation: number, - ) {} - - getLabel() { - return `${this.a} ${this.operator} ${this.b} = ${this.expected}`; - } - - async run(item: vscode.TestItem, options: vscode.TestRun): Promise { - const start = Date.now(); - - console.log(item); - - let run = await cli.exec(`/run ${item.uri}`); - - console.log(run); - - const actual = this.evaluate(); - const duration = Date.now() - start; - - if (actual === this.expected) { - options.passed(item, duration); - } else { - const message = vscode.TestMessage.diff( - `Expected ${item.label}`, - String(this.expected), - String(actual), - ); - message.location = new vscode.Location(item.uri!, item.range!); - options.failed(item, message, duration); - } - } - - private evaluate() { - switch (this.operator) { - case '-': - return this.a - this.b; - case '+': - return this.a + this.b; - case '/': - return Math.floor(this.a / this.b); - case '*': - return this.a * this.b; - } - } -} diff --git a/src/tests.ts b/src/tests.ts new file mode 100644 index 0000000..828f87a --- /dev/null +++ b/src/tests.ts @@ -0,0 +1,149 @@ +import * as vscode from 'vscode'; +import { TDInstance } from './cli'; +import { beautifyFilename, getUri } from './utils'; + +const FLAT = false; +const testGlobPattern = 'testdriver/**/*.{yml,yaml}'; + +export const setupTests = () => { + const controller = vscode.tests.createTestController( + 'testdriver-test-controller', + 'TestDriver', + ); + discoverAndWatchTests(controller); + setupRunProfiles(controller); + + return controller; +}; + +const discoverAndWatchTests = async (controller: vscode.TestController) => { + if (!vscode.workspace.workspaceFolders) { + return []; + } + vscode.workspace.workspaceFolders.map(async (workspaceFolder) => { + const pattern = new vscode.RelativePattern( + workspaceFolder, + testGlobPattern, + ); + const watcher = vscode.workspace.createFileSystemWatcher(pattern); + + const refresh = () => refreshTests(controller, workspaceFolder); + + watcher.onDidCreate(refresh); + watcher.onDidDelete(refresh); + refresh(); + }); +}; + +const refreshTests = async ( + controller: vscode.TestController, + workspaceFolder: vscode.WorkspaceFolder, +) => { + const pattern = new vscode.RelativePattern(workspaceFolder, testGlobPattern); + const matches = await vscode.workspace.findFiles(pattern); + const filteredMatches = matches + .map((uri) => ({ + uri, + parts: vscode.workspace + .asRelativePath(uri, false) + .replace(/^testdriver\/?/, '') + .split('/'), + })) + .filter(({ parts }) => { + return !['', 'generate', 'screenshots'].includes(parts[0]); + // && !/\.tmp\.ya?ml$/i.test(parts[parts.length - 1]) + }); + + controller.items.forEach((item) => { + controller.items.delete(item.id); + }); + + if (FLAT) { + filteredMatches.forEach(({ uri: file }) => { + controller.items.add( + controller.createTestItem( + file.toString(), + beautifyFilename(file.toString()), + file, + ), + ); + }); + } else { + const testFiles = filteredMatches.map(({ parts }) => + parts.map((_, index) => parts.slice(0, index + 1).join('/')), + ); + + for (const test of testFiles) { + let cursor = controller.items; + for (const path of test) { + const uri = getUri(`testdriver/${path}`, workspaceFolder); + const id = uri.toString(); + if (!cursor.get(id)) { + cursor.add( + controller.createTestItem(id, beautifyFilename(path), uri), + ); + } + + cursor = cursor.get(id)!.children; + } + } + } +}; + +const setupRunProfiles = (controller: vscode.TestController) => { + async function runHandler( + request: vscode.TestRunRequest, + token: vscode.CancellationToken, + ) { + const run = controller.createTestRun(request); + const queue: vscode.TestItem[] = []; + const addToQueue = (test: vscode.TestItem) => { + if (request.exclude?.includes(test)) { + return; + } + queue.push(test); + run.enqueued(test); + }; + + if (request.include?.length) { + request.include.forEach((test) => addToQueue(test)); + } else { + controller.items.forEach((test) => addToQueue(test)); + } + + while (queue.length > 0 && !token.isCancellationRequested) { + const test = queue.pop()!; + + if (test.children.size === 0) { + run.started(test); + const workspaceFolder = vscode.workspace.workspaceFolders!.find((ws) => + test.uri?.fsPath.startsWith(ws.uri.fsPath), + )!; + + const relativePath = vscode.workspace.asRelativePath(test.uri!, false); + const abortController = new AbortController(); + token.onCancellationRequested(() => abortController.abort()); + + const instance = new TDInstance(workspaceFolder); + await instance + .run(`/run ${relativePath}`, { signal: abortController.signal }) + .then(() => run.passed(test)) + .catch((err) => run.failed(test, new vscode.TestMessage(err.message))) + .finally(() => instance.destroy()); + + console.log(`Test ${test.id} finished`); + } else { + test.children.forEach((test) => addToQueue(test)); + } + } + + // Make sure to end the run after all tests have been executed: + run.end(); + } + + controller.createRunProfile( + 'Run', + vscode.TestRunProfileKind.Run, + (request, token) => runHandler(request, token), + ); +}; diff --git a/src/utils.ts b/src/utils.ts index 2eba9ee..c7e7492 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,33 +1,7 @@ -import { exec } from 'node:child_process'; +import path from 'node:path'; import * as vscode from 'vscode'; - -export function getActiveWorkspaceFolder(): vscode.WorkspaceFolder | undefined { - const workspaceFolders = vscode.workspace.workspaceFolders; - - // No workspace folders open - if (!workspaceFolders || workspaceFolders.length === 0) { - return undefined; - } - - // Only one folder in workspace - if (workspaceFolders.length === 1) { - return workspaceFolders[0]; - } - - // Multiple folders in workspace - determine which is active based on active editor - const activeEditor = vscode.window.activeTextEditor; - if (activeEditor) { - const activeDocUri = activeEditor.document.uri; - const activeFolder = vscode.workspace.getWorkspaceFolder(activeDocUri); - if (activeFolder) { - return activeFolder; - } - } - - // Fallback to first workspace folder if no active editor or if active file - // isn't within any workspace folder - return workspaceFolders[0]; -} +import { exec } from 'node:child_process'; +import { EventEmitter } from 'node:events'; export function run(command: string, { cwd }: { cwd?: string } = {}) { return new Promise<{ stdout: string; stderr: string; error: Error | null }>( @@ -42,3 +16,238 @@ export function run(command: string, { cwd }: { cwd?: string } = {}) { }, ); } + +export function exists(uri: vscode.Uri) { + return vscode.workspace.fs.stat(uri).then( + () => true, + () => false, + ); +} + +export function getUri( + relativePath: string, + workspaceFolder: vscode.WorkspaceFolder, +) { + return vscode.Uri.file(path.join(workspaceFolder.uri.fsPath, relativePath)); +} + +export async function isFileType( + type: vscode.FileType, + file: vscode.Uri, +): Promise { + return vscode.workspace.fs.stat(file).then( + (stat) => stat.type === type, + () => null, + ); +} + +export function beautifyFilename(name: string) { + return name + .split('/') + .pop()! + .replace(/\.[a-zA-Z0-9]$/, '') + .replace(/[_-]/g, ' '); +} + +export const getActiveWorkspaceFolder = () => { + const workspaces = vscode.workspace.workspaceFolders; + if (!workspaces || workspaces.length === 0) { + return null; + } + + const activeEditorPath = vscode.window.activeTextEditor?.document.uri.path; + + if (!activeEditorPath) { + return workspaces[0]; + } + + const matchingWorkspace = workspaces.find((wsFolder) => { + const relative = path.relative(wsFolder.uri.fsPath, activeEditorPath); + return relative && !relative.startsWith('..') && !path.isAbsolute(relative); + }); + + return matchingWorkspace ?? workspaces[0]; +}; + +type Markdown = string; +interface MultilineCodeblock { + type?: string; + content: string; +} + +export type MarkdownParserEvent = Markdown | MultilineCodeblock; +export class MarkdownStreamParser extends EventEmitter<{ + markdown: [Markdown]; + codeblock: [MultilineCodeblock]; +}> { + inCodeBlock = false; + codeBlockLang = ''; + codeBlockContent = ''; + markdownContent = ''; + backtickCount = 0; + potentialCodeBlockStart = false; + potentialCodeBlockEnd = false; + languageCapture = false; + + // Buffer for recent characters + recentChars: string[] = []; + MAX_RECENT_CHARS = 10; + constructor() { + super(); + } + + // Process a single character + processChar(char: string) { + // Add to recent characters buffer for lookahead/lookbehind + this.recentChars.push(char); + if (this.recentChars.length > this.MAX_RECENT_CHARS) { + this.recentChars.shift(); + } + + // Handle backticks for code block detection + if (char === '`') { + this.backtickCount++; + + // If we get 3 backticks, we're either entering or exiting a code block + if (this.backtickCount === 3) { + if (this.inCodeBlock) { + // When we detect closing backticks, immediately remove backticks that might have been added to content + // For example if we have "content```" in the buffer, we need to trim the backticks + if (this.codeBlockContent.endsWith('``')) { + this.codeBlockContent = this.codeBlockContent.slice(0, -2); + } else if (this.codeBlockContent.endsWith('`')) { + this.codeBlockContent = this.codeBlockContent.slice(0, -1); + } + this.potentialCodeBlockEnd = true; + } else { + this.potentialCodeBlockStart = true; + this.languageCapture = true; // Start capturing language after ``` + } + } + } else { + // Reset backtick counter if we see a non-backtick + if (this.backtickCount > 0 && this.backtickCount < 3) { + // If we saw some backticks but not 3, add them to the content + if (this.inCodeBlock) { + this.codeBlockContent += '`'.repeat(this.backtickCount); + } else { + this.markdownContent += '`'.repeat(this.backtickCount); + } + } + this.backtickCount = 0; + + // Handle potential code block starts + if (this.potentialCodeBlockStart) { + if (this.languageCapture) { + // If we're capturing a language and see whitespace, end language capture + if (/\s/.test(char)) { + this.languageCapture = false; + } else { + // Otherwise add to language string + this.codeBlockLang += char; + return; // Skip adding to content + } + } + + // If we see a newline, confirm code block start + if (char === '\n') { + // Emit markdown content collected so far + if (this.markdownContent.trim()) { + this.emit('markdown', this.markdownContent); + } + + this.markdownContent = ''; + this.inCodeBlock = true; + this.potentialCodeBlockStart = false; + return; // Skip adding this newline to content + } + } + + // Handle potential code block ends + if (this.potentialCodeBlockEnd) { + // If we see a newline or carriage return, confirm code block end + if (char === '\n' || char === '\r') { + // Emit the code block WITHOUT the closing backticks + const codeBlockObj = this.codeBlockLang + ? { type: this.codeBlockLang, content: this.codeBlockContent } + : { content: this.codeBlockContent }; + + this.emit('codeblock', codeBlockObj); + + this.codeBlockContent = ''; + this.codeBlockLang = ''; + this.inCodeBlock = false; + this.potentialCodeBlockEnd = false; + + // Add this newline to the markdown content + this.markdownContent = '\n'; + return; + } else { + // If we see something other than a newline after ```, + // it wasn't a code block end + this.potentialCodeBlockEnd = false; + this.codeBlockContent += '```' + char; + return; + } + } + + // Add character to the appropriate buffer + if (this.inCodeBlock) { + this.codeBlockContent += char; + } else { + this.markdownContent += char; + } + } + } + + // Call this when the stream ends to emit any remaining content + end() { + // First check if we're in a potential code block end state + // This happens when the input ends with ``` + if (this.potentialCodeBlockEnd && this.inCodeBlock) { + // Emit the code block WITHOUT including the closing backticks + const codeBlockObj = this.codeBlockLang + ? { type: this.codeBlockLang, content: this.codeBlockContent } + : { content: this.codeBlockContent }; + + this.emit('codeblock', codeBlockObj); + + // Reset state + this.inCodeBlock = false; + this.codeBlockLang = ''; + this.codeBlockContent = ''; + this.markdownContent = ''; + this.backtickCount = 0; + this.potentialCodeBlockEnd = false; + return; + } + + // Handle any other remaining backticks + if (this.backtickCount > 0) { + if (this.inCodeBlock) { + this.codeBlockContent += '`'.repeat(this.backtickCount); + } else { + this.markdownContent += '`'.repeat(this.backtickCount); + } + } + + // Emit any remaining content + if (this.inCodeBlock) { + const codeBlockObj = this.codeBlockLang + ? { type: this.codeBlockLang, content: this.codeBlockContent } + : { content: this.codeBlockContent }; + + this.emit('codeblock', codeBlockObj); + } else if (this.markdownContent.trim()) { + this.emit('markdown', this.markdownContent); + } + + // Reset state + this.inCodeBlock = false; + this.codeBlockLang = ''; + this.codeBlockContent = ''; + this.markdownContent = ''; + this.backtickCount = 0; + this.potentialCodeBlockEnd = false; + } +} diff --git a/tsconfig.json b/tsconfig.json index 0595598..b14e9cd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,9 @@ "outDir": "out", "jsx": "react", "jsxFactory": "vscpp", - "jsxFragmentFactory": "vscppf" + "jsxFragmentFactory": "vscppf", + "skipLibCheck": true, + "rootDirs": ["src"] /* Additional Checks */ // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ From 425f744e6c1a5240b75f6a822408206db90b5435 Mon Sep 17 00:00:00 2001 From: Yassine El Khattabi Date: Fri, 4 Apr 2025 16:20:12 +0000 Subject: [PATCH 04/32] Debug testdriverai --- src/cli.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 45a6149..bed9aa0 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -128,8 +128,8 @@ export class TDInstance extends EventEmitter { .on('idle', () => console.log('[debug:idle]')) .on('busy', () => console.log('[debug:busy]')) .on('exit', (code) => console.log('[debug:exit]', code)) - // .on('stdout', (data) => process.stdout.write(data)) - // .on('stderr', (data) => process.stderr.write(data)) + .on('stdout', (data) => process.stdout.write(data)) + .on('stderr', (data) => process.stderr.write(data)) .on('output', (data) => process.stdout.write(data)); } From a2afd1f0dc05750993c22bc4d8f5aff836f59e6f Mon Sep 17 00:00:00 2001 From: Yassine El Khattabi Date: Fri, 4 Apr 2025 16:23:05 +0000 Subject: [PATCH 05/32] Fix typo --- src/commands/chat.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/chat.ts b/src/commands/chat.ts index 5668924..8f2595b 100644 --- a/src/commands/chat.ts +++ b/src/commands/chat.ts @@ -30,7 +30,7 @@ export const handleTDCommandInChat = async ( ? 'workbench.action.chat.new' : 'workbench.action.chat.open'; - await vscode.commands.executeCommand(`/${command}`, options); + await vscode.commands.executeCommand(`${command}`, options); }; export const testdriverCommand = (command: 'dry' | 'try') => async () => { From 57588fe81a57ade6b103b7e37df922c2587dc638 Mon Sep 17 00:00:00 2001 From: Yassine El Khattabi Date: Fri, 4 Apr 2025 16:49:00 +0000 Subject: [PATCH 06/32] Add tests related output and handle null exit code --- src/cli.ts | 2 +- src/tests.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/cli.ts b/src/cli.ts index bed9aa0..a22d044 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -172,7 +172,7 @@ export class TDInstance extends EventEmitter { }; this.once('exit', (code) => - code === 0 ? resolve(result) : reject(new Error('Process exited')), + code ? reject(new Error('Process exited')) : resolve(result), ); const parser = new MarkdownStreamParser(); diff --git a/src/tests.ts b/src/tests.ts index 828f87a..abc1217 100644 --- a/src/tests.ts +++ b/src/tests.ts @@ -125,6 +125,9 @@ const setupRunProfiles = (controller: vscode.TestController) => { token.onCancellationRequested(() => abortController.abort()); const instance = new TDInstance(workspaceFolder); + instance.on('output', (data) => { + run.appendOutput(data.replace(/(? run.passed(test)) From 4ee297658c6c4919e8b7408cd81bf7eea9c24199 Mon Sep 17 00:00:00 2001 From: Yassine El Khattabi Date: Fri, 4 Apr 2025 16:58:34 +0000 Subject: [PATCH 07/32] emit show:vm event --- src/cli.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/cli.ts b/src/cli.ts index a22d044..4174b09 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -9,6 +9,7 @@ type IPCType = InferType; const { IPC } = nodeIPC; interface EventsMap { + vm_url: [string]; stdout: [string]; stderr: [string]; exit: [number | null]; @@ -105,6 +106,10 @@ export class TDInstance extends EventEmitter { break; case 'output': this.emit('output', data as string); + break; + case 'show:vm': + this.emit('vm_url', data as string); + break; } }; From 8662d660114f59d5cb91da5868804d94744d2bc2 Mon Sep 17 00:00:00 2001 From: Ian Jennings Date: Fri, 4 Apr 2025 12:11:36 -0500 Subject: [PATCH 08/32] respond to global chat --- src/chat.ts | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/chat.ts b/src/chat.ts index c9b641a..fa6aede 100644 --- a/src/chat.ts +++ b/src/chat.ts @@ -1,6 +1,7 @@ import * as vscode from 'vscode'; import { TDInstance } from './cli'; import { getActiveWorkspaceFolder } from './utils'; +import spec from './spec'; export const PARTICIPANT_ID = 'testdriver.driver'; @@ -54,5 +55,45 @@ const handler: vscode.ChatRequestHandler = async ( stream.progress('Unsupported command: ' + request.command); } return; + } else { + + stream.progress('Staring my engine...'); + + console.log(_context.history) + + + + try { + const messages = [ + vscode.LanguageModelChatMessage.User(spec), ]; + + // get all the previous participant messages + const previousMessages = _context.history.filter( + h => h instanceof vscode.ChatResponseTurn + ); + + // add the previous messages to the messages array + previousMessages.forEach(m => { + let fullMessage = ''; + m.response.forEach(r => { + const mdPart = r as vscode.ChatResponseMarkdownPart; + fullMessage += mdPart.value.value; + }); + messages.push(vscode.LanguageModelChatMessage.Assistant(fullMessage)); + }); + + // add in the user's message + messages.push(vscode.LanguageModelChatMessage.User(request.prompt)); + + // send the request + const chatResponse = await request.model.sendRequest(messages, {}, token); + + for await (const fragment of chatResponse.text) { + stream.markdown(fragment); + } + + } catch (err) { + console.log('err', err); + } } }; From 086e78fdcdb7c3ecb708f0305c7c0f345939bfda Mon Sep 17 00:00:00 2001 From: Yassine El Khattabi Date: Fri, 4 Apr 2025 17:47:35 +0000 Subject: [PATCH 09/32] Add test output to the output terminal --- src/tests.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/tests.ts b/src/tests.ts index abc1217..9cec941 100644 --- a/src/tests.ts +++ b/src/tests.ts @@ -125,11 +125,17 @@ const setupRunProfiles = (controller: vscode.TestController) => { token.onCancellationRequested(() => abortController.abort()); const instance = new TDInstance(workspaceFolder); - instance.on('output', (data) => { - run.appendOutput(data.replace(/(? { + run.appendOutput(data.replace(/(? { + run.appendOutput(data.replace(/(? run.passed(test)) .catch((err) => run.failed(test, new vscode.TestMessage(err.message))) .finally(() => instance.destroy()); From 102b366070a25dce05e5a8c391adbd3bc92be54c Mon Sep 17 00:00:00 2001 From: Yassine El Khattabi Date: Fri, 4 Apr 2025 17:50:12 +0000 Subject: [PATCH 10/32] Remove duplicate debug output --- src/cli.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 4174b09..a67c21a 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -134,8 +134,8 @@ export class TDInstance extends EventEmitter { .on('busy', () => console.log('[debug:busy]')) .on('exit', (code) => console.log('[debug:exit]', code)) .on('stdout', (data) => process.stdout.write(data)) - .on('stderr', (data) => process.stderr.write(data)) - .on('output', (data) => process.stdout.write(data)); + .on('stderr', (data) => process.stderr.write(data)); + // .on('output', (data) => process.stdout.write(data)); } async run( From c436038f01f78d97df1e4b96eba09151feebb953 Mon Sep 17 00:00:00 2001 From: Ian Jennings Date: Fri, 4 Apr 2025 18:51:31 -0500 Subject: [PATCH 11/32] drastically improve testdriverai --- src/chat.ts | 3 +- src/spec.ts | 2367 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 2367 insertions(+), 3 deletions(-) diff --git a/src/chat.ts b/src/chat.ts index fa6aede..5113c1a 100644 --- a/src/chat.ts +++ b/src/chat.ts @@ -6,7 +6,8 @@ import spec from './spec'; export const PARTICIPANT_ID = 'testdriver.driver'; export function registerChatParticipant(_context: vscode.ExtensionContext) { - vscode.chat.createChatParticipant(PARTICIPANT_ID, handler); + let participant = vscode.chat.createChatParticipant(PARTICIPANT_ID, handler); + participant.iconPath = vscode.Uri.joinPath(_context.extensionUri, 'icon.png'); } const handler: vscode.ChatRequestHandler = async ( diff --git a/src/spec.ts b/src/spec.ts index 61da310..3f6c053 100644 --- a/src/spec.ts +++ b/src/spec.ts @@ -1,8 +1,23 @@ export default `You are TestDriver.ai, the best quality assurance engineer in the world. Your job is help the user write tests. You have the special ability to understand whats on the users computer screen and help them write tests for it. All of your tests are in a special YML format. YML has commands and steps. Every new step that is copied from the chat should almost always be appended to the end of the file. -The useris going to be running the commands you give them. These commands should be in files in the \`testdriver\` directory in the root of their project. +The user is chatting with you in a GitHub Copilot window. -Build tests by talking to @TestDriver or using the \`testdriverai\` command in the terminal. +There are special commands the user can use to run code snippets. These are: + +- /dry - testdriver looks at the screen, generates a test, and shows the code in a code block +- /try - testdriver looks at the screen, generates a test, and runs it + +These commands will be run in a sandbox runner if \`TD_VM=true\` in the current environment. Otherwise, they will be run on the user's computer. + +If the user wants to make a test, they most likely want to use the \`/try\` command to see something happen. If the test goes off the rails, they can use the \`/dry\` command to experiment before running the test. + +Successful test steps should be appended to a file so the user can run them back. They can run the tests using the test tab in VSCode, or with \`testdriver run\` or \`testdriver run \`. The filename is optional and defaults to \`testdriver.yaml\`. + +We highly recommended using testdriver sandbox runners and an API key for most websites. If the user wants to test a desktop app or chrome extension, they should use the local agent. The local agent is a special version of testdriver that runs on the user's computer. It has access to the user's screen and can run tests on any application. + +Everything after this is a spec for the testdriver framework and can not be run within this chat window. They must be run using the testdriverai CLI or the testdriver GitHub action. + +This is the spec for the testdriverai cli installed via npm. It is a command line interface for the testdriver framework. It is used to run tests, generate tests, and edit tests.: Here are the commands you can use: Command: testdriverai [init, run, edit] [yaml filepath] @@ -154,4 +169,2352 @@ outputs: runs: using: node16 main: ./dist/index.js + +This is a full export of our documentation + +# Table of Contents + +- [Quickstart](#quickstart) +- [Generate a Test Suite](#generate-a-test-suite) +- [Test Steps](#test-steps) +- [Agent](#agent) +- [Screen Recording Permissions (Mac Only)](#screen-recording-permissions-mac-only) +- [FAQ](#faq) +- [Overview](#overview) +- [Pricing](#pricing) +- [Comparison](#comparison) +- [30x30 Promotion](#30x30-promotion) +- [Local Agent Setup](#local-agent-setup) +- [Prompting](#prompting) +- [Getting an API Key](#getting-an-api-key) +- [GitHub Actions](#github-actions) +- [Debugging Test Runs](#debugging-test-runs) +- [Monitoring Performance](#monitoring-performance) +- [GitHub Action Setup](#github-action-setup) +- [Prerun Scripts](#prerun-scripts) +- [Environment Config](#environment-config) +- [Parallel Testing](#parallel-testing) +- [Storing Secrets](#storing-secrets) +- [Optimizing Performance](#optimizing-performance) +- [Action Output](#action-output) +- [Examples](#examples) +- [Test Generation](#test-generation) +- [Importing Tests](#importing-tests) +- [Desktop Apps](#desktop-apps) +- [Secure Log In](#secure-log-in) +- [Interactive Commands](#interactive-commands) +- [CLI](#cli) +- [assert](#assert) +- [exec](#exec) +- [focus-application](#focus-application) +- [hover-image](#hover-image) +- [match-image](#match-image) +- [hover-text](#hover-text) +- [if](#if) +- [press-keys](#press-keys) +- [remember](#remember) +- [run](#run) +- [scroll](#scroll) +- [scroll-until-image](#scroll-until-image) +- [scroll-until-text](#scroll-until-text) +- [type](#type) +- [wait](#wait) +- [wait-for-image](#wait-for-image) +- [wait-for-text](#wait-for-text) +- [/assert](#assert) +- [/undo](#undo) +- [/save](#save) +- [/run](#run) +- [/generate](#generate) +- [testdriverai init](#testdriverai-init) +- [testdriverai [file]](#testdriverai-file) +- [testdriverai run [file]](#testdriverai-run-file) +- [Action](#action) +- [Dashboard](#dashboard) + +--- + + +# Quickstart + +Source: https://docs.testdriver.ai + +[NextFAQ](/overview/faq)Last updated 4 days ago + +Was this helpful? + +--- + + +# Generate a Test Suite + +Source: https://docs.testdriver.ai/guides/generate-a-test-suite + +[Previous30x30 Promotion](/pro-setup/30x30-promotion)[NextLocal Agent Setup](/guides/local-agent-setup)Last updated 4 days ago + +Was this helpful? + +--- + + +# Test Steps + +Source: https://docs.testdriver.ai/reference/test-steps + +TestDriver will worry about generating and maintaining tests for the most part. However, if you'd like to edit tests or gain a better understanding of what's going on you can find all of the \`command\`s in this secion. + +As for YML format, here is an example of a valid \`yml\` file: + +\`\`\` +version: 4.0.0 +steps: + - prompt: enter fiber.google.com in url + commands: + - command: focus-application + name: Google Chrome + - command: hover-text + text: Search Google or type a URL + description: main google search + action: click + - command: type + text: fiber.google.com + - command: press-keys + keys: + - enter + - prompt: enter a fake address and check availability + commands: + - command: focus-application + name: Google Chrome + - command: hover-text + text: Enter your address + description: address input field + action: click + - command: type + text: 123 Fake Street + - command: hover-text + text: ZIP + description: ZIP code input field + action: click + - command: type + text: 12345 + - command: hover-text + text: Check availability + description: check availability button + action: click + - prompt: assert a familiy appears on screen + commands: + - command: focus-application + name: Google Chrome + - command: assert + expect: a family appears on screen +\`\`\` +[PreviousMonitoring Performance](/guides/monitoring-performance)[Nextassert](/reference/test-steps/assert)Last updated 3 months ago + +Was this helpful? + +--- + + +# Agent + +Source: https://docs.testdriver.ai/security-and-privacy/agent + +## Source + +The TestDriver agent is open-source and available on NPM. You can browser the source and see all the data collected and how everything works. + +## API + +The TestDriver agent does not contain any AI models within it. Instead, it uploads desktop context to our API which uses that context to make decisions about what actions to perform. + +Our API makes use of OpenAI models behind the scenes. You can learn more about OpenAI and privacy in [their privacy center](https://privacy.openai.com/). + +## Desktop Context Collected + +During execution the TestDriver agent uploads the following information to our API + +* User input prompts +* The active window and other windows that may be open (including application and window titles) +* System information +* The mouse position +* Screenshots of the desktop + +**With the exception of desktop screenshots**, desktop context is persisted into our database. + +Desktop screenshots are uploaded to our server but are not persisted in our database. + +## Desktop Screenshots + +TestDriver frequently takes screenshots of the desktop to provide our AI with decisions making context. You will not be prompted. Desktop screenshots are uploaded to our API for processing but are not persisted. + +The TestDriver Agent will only take screenshots of the primary display. **For complete privacy, we recommend running TestDriver within a virtual machine on your desktop.** + +TestDriver can not operate without visual context. Do not install TestDriver if you do not want to capture images of the desktop. + +## Active Window + +Information about the open windows on the desktop is reported by the [active-window](https://www.npmjs.com/package/active-win) module. + +## System Information + +Information about the computer system running testdriver is reported by the [systeminformation](https://www.npmjs.com/package/systeminformation) module. + +## User Prompts + +The prompts you input to TestDriver are uploaded to our API and persisted in a database. We store this data to provide our AI with a history of context. + +## Additional Analytics + +When running \`testdriver init\` you'll be asked if you'd like to share additional analytics. Sharing usage analytics is opt-in, this extra data will not be collected unless explicitly set in your environment. + +If you would like to disable additional analytics, you can set \`TD_ANALYTICS\` within your environment. + +\`\`\` +TD_ANALYTICS=false +\`\`\` +## Rate Limiting and Other Restrictions + +While the TestDriver Agent is free, we do reserve the right to rate limit or restrict usage by IP address for any reason. + +[Previoustestdriverai run [file]](/reference/cli/testdriverai-run-file)[NextAction](/security-and-privacy/action)Last updated 7 months ago + +Was this helpful? + +--- + + +# Screen Recording Permissions (Mac Only) + +Source: https://docs.testdriver.ai/faq/screen-recording-permissions-mac-only + +[PreviousDashboard](/security-and-privacy/dashboard)Last updated 6 months ago + +Was this helpful? + +--- + + +# FAQ + +Source: https://docs.testdriver.ai/overview/faq + +## 🔧 **Product Capabilities** + +* **What is TestDriver?** +TestDriver is an AI-powered testing platform that simulates user interactions to automate end-to-end testing for web, desktop, and mobile applications. +* **How does TestDriver work?** +It interprets high-level prompts, interacts with interfaces like a user would, and verifies expected outcomes using assertions and visual validation. +* **What platforms does TestDriver support?** +TestDriver supports Windows, Mac, Linux desktop apps, web browsers, and mobile interfaces (via emulator or device farm). +* **Can it be used for exploratory testing?** +Yes. TestDriver can autonomously navigate the application to discover potential issues or generate new test cases. +* **Can it test desktop applications?** +Yes. It supports testing native desktop applications by simulating mouse and keyboard input and identifying UI elements. +* **Can it test mobile apps?** +Yes, via mobile emulators or integration with device farms. + +--- + +## 🤖 **Test Creation and Generation** + +* **Can TestDriver generate tests automatically?** +Yes, it explores the app and creates test cases based on UI flows and user interactions. +* **Can I create tests from natural language prompts?** +Yes. You can write high-level instructions in plain language, and TestDriver will interpret and build tests from them. +* **Can it generate tests from user stories or documentation?** +Yes. It can use minimal descriptions to produce complete test cases. +* **Can it turn recorded user sessions into tests?** +Yes, in supported environments, TestDriver can generate test steps from interaction logs or screen recordings. + +--- + +## 🛠️ **Test Maintenance and Resilience** + +* **What happens when the UI changes?** +TestDriver adapts using AI—if a button or label changes, it can often infer the correct action without breaking. +* **Do I need to rewrite tests often?** +No. TestDriver reduces maintenance by handling common UI changes automatically. +* **How does it handle flaky tests?** +It retries failed actions, assigns confidence scores, and logs inconsistencies so you can investigate root causes. +* **How are tests updated over time?** +You can regenerate them using updated prompts or manually edit the test specs. + +--- + +## 🚨 **Failures, Debugging, and Feedback** + +* **How does TestDriver report test failures?** +It provides detailed logs, screenshots, console output, and visual diffs. +* **What happens when a test fails?** +It stops execution, flags the failing step, and provides context for debugging. +* **Can I view why a test failed?** +Yes. You can view step-by-step logs, network traffic, DOM state, and video playback of the test run. +* **Can it automatically retry failed actions?** +Yes. You can configure retry behavior for individual steps or full tests. + +--- + +## 🚀 **Performance and Parallelism** + +* **Can I run tests in parallel?** +Yes. TestDriver supports parallel execution using multiple VMs or containers. +* **Can I track performance metrics during testing?** +Yes. It can log CPU, memory, load times, and frame rates to help catch performance regressions. + +--- + +## 🔍 **Advanced Testing Features** + +* **Can it validate non-deterministic output?** +Yes. It uses AI assertions to verify outcomes even when outputs vary (e.g., generated text or dynamic UIs). +* **Can it test workflows with variable inputs?** +Yes. It supports data-driven tests using parameterized inputs. +* **Can it test file uploads and downloads?** +Yes. TestDriver can interact with file pickers and validate uploaded/downloaded content. +* **Can it generate tests for PDFs or document output?** +Yes. It can open and verify generated files for expected text or formatting. +* **Can I trigger tests based on pull requests or merges?** +Yes. You can integrate TestDriver with your CI to trigger runs via GitHub Actions or other CI/CD tools. + +--- + +## 🧩 **Integration and Setup** + +* **Does it integrate with CI/CD tools?** +Yes. TestDriver integrates with pipelines like GitHub Actions, GitLab CI, and CircleCI. +* **Can I integrate TestDriver with Jira, Slack, etc.?** +Yes. You can receive alerts and sync test results with third-party tools via API/webhooks. +* **Does it support cloud and local environments?** +Yes. You can run tests locally or in the cloud using ephemeral VMs for clean state testing. +* **Does it work with existing test frameworks?** +It can complement or convert some existing test cases into its format, though full conversion depends on compatibility. + +--- + +## 📊 **Test Coverage and Effectiveness** + +* **How does TestDriver measure test coverage?** +It tracks UI paths, element interaction frequency, and application state changes to infer coverage. +* **Can it suggest missing test scenarios?** +Yes. Based on interaction patterns and user behavior, it can propose additional test cases. +* **Can it analyze test stability over time?** +Yes. You can view trends in pass/fail rates and test execution consistency. + +--- + +## 🔒 **Security and Compliance** + +* **Is it safe to test sensitive data?** +Yes. TestDriver supports variable obfuscation, secure containers, and test data isolation. +* **Can I avoid using production data in tests?** +Yes. You can configure mock data, sanitize logs, and use test-specific environments. + +--- + +## 🧠 **AI Behavior and Prompting** + +* **How does the AI understand what to test?** +It uses language models to interpret your goals, element names, and interface cues to perform tasks. +* **Can I adjust how the AI interprets my prompt?** +Yes. You can rewrite prompts, add constraints, or review and tweak auto-generated steps. +* **Can I see what the AI is doing behind the scenes?** +Yes. You can inspect the resolved steps, see element matches, and adjust test flows before execution. + +--- + +## 📦 **Use Cases and Scenarios** + +* **Can I use TestDriver to test new features?** +Yes. It’s great for validating changes, ensuring no regressions, and verifying rollout configurations. +* **Can it test seasonal or time-based behaviors?** +Yes. You can schedule tests or run them under specific date/time settings to verify time-sensitive logic. + +[PreviousQuickstart](/)[NextOverview](/overview/overview)Last updated 1 day ago + +Was this helpful? + +--- + + +# Overview + +Source: https://docs.testdriver.ai/overview/overview + +TestDriver isn't like any test framework you've used before - it's more like your own QA employee with their own development environment. + +1. Tell TestDriver what to do in natural language +2. TestDriver looks at the screen and uses mouse and keyboard emulation to accomplish the goal + + TestDriver is "selectorless" testing. It doesn't use selectors or static analysis. + +## Advantages + +TestDriver then uses AI vision and hardware emulation to simulate real user on their own computer. This has three main advantages: + +* **Easier set up**: No need to add test IDs or craft complex selectors +* **Less Maintenance**: Tests don't break when code changes +* **More Power:** TestDriver can test any application and control any OS setting + +## Just tell TestDriver what to do + +Use our CLI to tell TestDriver what to do, like so: + +\`\`\` +> open google chrome +> navigate to airbnb.com +> search for destinations in austin tx +> click check in +> select august 8 +\`\`\` +## Possibilities + +As you can imagine, a specialized QA agent with it's own computer is extremely powerful. TestDriver can: + +* Test any user flow on any website in any browser +* Clone, build, and test any desktop app +* Render multiple browser windows and popups like 3rd party auth +* Test \`\`, \`