diff --git a/.env b/.env new file mode 100644 index 0000000..8cafc30 --- /dev/null +++ b/.env @@ -0,0 +1,6 @@ +SERVER_URL='127.0.0.1' +HASH_PATH_MAP_KEY='hashPathMap' +SMELL_CACHE_KEY='smellCache' +WORKSPACE_METRICS_DATA='metricsData' +WORKSPACE_CONFIGURED_PATH='workspaceConfiguredPath' +UNFINISHED_REFACTORING='unfinishedRefactoring' diff --git a/.github/workflows/jest-tests.yaml b/.github/workflows/jest-tests.yaml new file mode 100644 index 0000000..c71a146 --- /dev/null +++ b/.github/workflows/jest-tests.yaml @@ -0,0 +1,33 @@ +name: Jest Tests and Coverage Check + +on: + pull_request: + types: [opened, reopened, synchronize] + branches: [dev] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Clean install + run: | + npm ci + + - name: Run Jest tests + run: npm test -- --coverage + + - name: Upload coverage report + if: always() + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: coverage/ diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 2612d6b..611807c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,26 +1,58 @@ +name: Publish Extension + on: push: - branches: - - main - release: - types: - - created + branches: [main] jobs: - build: + publish: runs-on: ubuntu-latest + permissions: + contents: write # Needed for tag/release creation + id-token: write # For OIDC auth + steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Install Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - - run: npm install - - run: npm install @vscode/vsce --save-dev - - name: Publish - if: startsWith(github.ref, 'refs/tags/') - run: npm run deploy - env: - VSCE_PAT: ${{ secrets.VSCE_PAT }} + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Full history needed for tagging + + - name: Get version + id: version + run: | + VERSION=$(node -p "require('./package.json').version") + echo "tag_name=v$VERSION" >> $GITHUB_OUTPUT + + - name: Create and push tag + run: | + git config --global user.name "GitHub Actions" + git config --global user.email "actions@github.com" + git tag ${{ steps.version.outputs.tag_name }} + git push origin ${{ steps.version.outputs.tag_name }} + + - name: Install dependencies + run: | + npm install + npm install -g @vscode/vsce + + - name: Package Extension + run: | + mkdir -p dist + vsce package --out ./dist/extension-${{ steps.version.outputs.tag_name }}.vsix + + - name: Create Draft Release + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ steps.version.outputs.tag_name }} + name: ${{ steps.version.outputs.tag_name }} + body: 'Release notes' + files: | + dist/extension-${{ steps.version.outputs.tag_name }}.vsix + draft: true + prerelease: false + - name: Publish to Marketplace + run: | + vsce publish -p $VSCE_PAT + env: + VSCE_PAT: ${{ secrets.VSCE_PAT }} diff --git a/.github/workflows/version-check.yaml b/.github/workflows/version-check.yaml new file mode 100644 index 0000000..a92a9a4 --- /dev/null +++ b/.github/workflows/version-check.yaml @@ -0,0 +1,59 @@ +name: PR Version Check + +on: + pull_request: + branches: [main] + +jobs: + validate_version: + runs-on: ubuntu-latest + steps: + - name: Checkout PR branch + uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + fetch-depth: 0 # Required for branch comparison + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - run: npm install compare-versions + + - name: Get PR version + id: pr_version + run: | + PR_VERSION=$(node -p "require('./package.json').version") + echo "pr_version=$PR_VERSION" >> $GITHUB_OUTPUT + + - name: Fetch main branch + run: git fetch origin main + + - name: Get main's version + id: main_version + run: | + MAIN_VERSION=$(git show origin/main:package.json | node -p "JSON.parse(require('fs').readFileSync(0)).version") + echo "main_version=$MAIN_VERSION" >> $GITHUB_OUTPUT + + - name: Validate version bump + run: | + # Write a temporary Node.js script for version comparison + cat > compare-versions.mjs << 'EOF' + import { compareVersions } from 'compare-versions'; + + const mainVersion = process.argv[2]; + const prVersion = process.argv[3]; + + console.log("Main version:", mainVersion) + console.log("PR version:", prVersion) + + if (compareVersions(prVersion, mainVersion) < 1) { + console.error(`::error::Version ${prVersion} must be greater than ${mainVersion}`); + process.exit(1); + } + EOF + + node compare-versions.mjs "${{ steps.main_version.outputs.main_version }}" "${{ steps.pr_version.outputs.pr_version }}" + + echo "✓ Version validated" diff --git a/.gitignore b/.gitignore index 0b60dfa..5516fa6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ dist node_modules .vscode-test/ *.vsix +coverage/ +.venv/ \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..d0a7784 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..77b3620 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,10 @@ +node_modules +dist +.vscode +.husky +media +.env +**/*.config.js +**/*.mjs' +**/*d.ts +coverage/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..21a17ba --- /dev/null +++ b/.prettierrc @@ -0,0 +1,10 @@ +{ + "bracketSpacing": true, + "printWidth": 85, + "semi": true, + "singleQuote": true, + "endOfLine": "auto", + "tabWidth": 2, + "trailingComma": "all", + "plugins": ["prettier-plugin-tailwindcss"] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 8a453d0..d962edb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,5 +9,18 @@ "dist": true }, // Turn off tsc task auto detection since we have the necessary tasks as npm scripts - "typescript.tsc.autoDetect": "off" + "typescript.tsc.autoDetect": "off", + "cSpell.words": [ + "ecooptimizer", + "occurences" + ], + "eslint.enable": true, + "eslint.lintTask.enable": true, + "eslint.validate": [ + "javascript", + "typescript", + "javascriptreact", + "typescriptreact" + ], + "files.eol": "\n", } diff --git a/.vscodeignore b/.vscodeignore index d255964..c80888d 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -1,14 +1,18 @@ -.vscode/** -.vscode-test/** -out/** -node_modules/** src/** -.gitignore -.yarnrc +.vscode +node_modules +package-lock.json +tsconfig.json webpack.config.js -vsc-extension-quickstart.md -**/tsconfig.json -**/eslint.config.mjs +eslint.config.mjs +.prettier* +.gitignore +run/** +.venv/** +test/** +.github/** +.husky/** +coverage/** **/*.map -**/*.ts -**/.vscode-test.* +.vscode-test.mjs +.env \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f03f0e..f977202 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -All notable changes to the "ecooptimizer-vs-code-plugin" extension will be documented in this file. +All notable changes to the "ecooptimizer" extension will be documented in this file. Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4d87f17 --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ +Copyright (c) 2024-2025 Ayushi Amin, Mya Hussain, Nivetha Kuruparan, Sevhena Walker, Tanveer Brar + +Permission is hereby granted, on a case-by-case basis, to specific individuals or organizations ("Licensee") to use and access the software and associated documentation files (the "Software") strictly for evaluation or development purposes. This permission is non-transferable, non-exclusive, and does not grant the Licensee any rights to modify, merge, publish, distribute, sublicense, sell, or otherwise exploit the Software in any manner without explicit prior written consent from the copyright holder. + +Any unauthorized use, modification, distribution, or sale of the Software is strictly prohibited and may result in legal action. + +The Software is provided "AS IS," without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, or non-infringement. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. \ No newline at end of file diff --git a/README.md b/README.md index cb7d990..c723736 100644 --- a/README.md +++ b/README.md @@ -1 +1,55 @@ -VS Code Plugin for Source Code Optimizer \ No newline at end of file +# EcoOptimizers - Sustainable Python Code Refactoring + +EcoOptimizers is a VS Code extension that detects and refactors inefficient Python code, reducing unnecessary computations and lowering CO₂ emissions. By identifying common code smells and providing automated refactoring suggestions, EcoOptimizers helps you write cleaner, more efficient, and environmentally friendly code. + +## Features + +- **Detect Code Smells** – Automatically analyze your Python code to find inefficiencies. +- **Refactor Code Smells** – Get instant suggestions and apply refactorings with ease. +- **Reduce CO₂ Emissions** – Improve computational efficiency and contribute to a greener future. +- **Seamless VS Code Integration** – Analyze and optimize your code directly within the editor. + +## Supported Code Smells + +EcoOptimizers detects and refactors the following common code smells: + +- **Cache Repeated Calls** – Identifies functions that repeatedly compute the same result and suggests caching techniques. +- **Long Lambda Functions** – Flags excessively long lambda expressions and converts them into named functions for readability and maintainability. +- **Use A Generator** – Suggests using generators instead of list comprehensions for memory efficiency. +- **Long Element Chain** – Detects deeply nested attribute accesses and recommends breaking them into intermediate variables for clarity. +- **Member Ignoring Method** – Identifies methods that do not use their class members and suggests converting them into static methods or external functions. +- **Long Message Chains** – Finds excessive method chaining and refactors them for better readability. +- **String Concatenation in Loop** – Detects inefficient string concatenation inside loops and suggests using lists or other optimized methods. +- **Long Parameter List** – Flags functions with too many parameters and suggests refactoring strategies such as grouping related parameters into objects. + +## How It Works + +1. **Detect Smells** – Run the EcoOptimizers analysis tool to scan your code for inefficiencies. +2. **Refactor Suggestions** – View recommended changes and apply them with a click. +3. **Optimize Your Code** – Enjoy cleaner, more efficient Python code with reduced computational overhead. + +## Demo Videos + +Watch EcoOptimizers in action: + +- [Detecting Code Smells](https://drive.google.com/file/d/1Uyz0fpqjWVZVe_WXuJLB0bTtzOvjhefu/view?usp=sharing) 🔍 +- [Refactoring Code Smells](https://drive.google.com/file/d/1LQFdnKhuZ7nQGFEXZl3HQtF3TFgMJr6F/view?usp=sharing) 🔧 + +## Installation + +1. Open VS Code. +2. Go to the Extensions Marketplace. +3. Search for **EcoOptimizers**. +4. Click **Install**. +5. Intall the `ecooptimizer` python package. + - run: `pip install ecooptimizer` + - run: `eco-ext` +6. Start optimizing your Python code! + +## Contribute + +EcoOptimizers is open-source! Help improve the extension by contributing to our GitHub repository: [GitHub Repository](https://github.com/ssm-lab/capstone--source-code-optimizer) + +--- + +🚀 Start writing cleaner, more efficient Python code today with EcoOptimizers! \ No newline at end of file diff --git a/assets/black_leaf.png b/assets/black_leaf.png new file mode 100644 index 0000000..fdd8163 Binary files /dev/null and b/assets/black_leaf.png differ diff --git a/assets/darkgreen_leaf.png b/assets/darkgreen_leaf.png new file mode 100644 index 0000000..1b5d1ea Binary files /dev/null and b/assets/darkgreen_leaf.png differ diff --git a/assets/eco-icon.png b/assets/eco-icon.png new file mode 100644 index 0000000..f5314b7 Binary files /dev/null and b/assets/eco-icon.png differ diff --git a/assets/eco_logo.png b/assets/eco_logo.png new file mode 100644 index 0000000..a718c53 Binary files /dev/null and b/assets/eco_logo.png differ diff --git a/assets/green_leaf.png b/assets/green_leaf.png new file mode 100644 index 0000000..4859246 Binary files /dev/null and b/assets/green_leaf.png differ diff --git a/assets/white_leaf.png b/assets/white_leaf.png new file mode 100644 index 0000000..429dc6d Binary files /dev/null and b/assets/white_leaf.png differ diff --git a/data/default_smells_config.json b/data/default_smells_config.json new file mode 100644 index 0000000..4c7e3df --- /dev/null +++ b/data/default_smells_config.json @@ -0,0 +1,101 @@ +{ + "use-a-generator": { + "message_id": "R1729", + "name": "Use A Generator (UGEN)", + "acronym": "UGEN", + "enabled": true, + "smell_description": "Using generators instead of lists reduces memory consumption and avoids unnecessary allocations, leading to more efficient CPU and energy use.", + "analyzer_options": {} + }, + "too-many-arguments": { + "message_id": "R0913", + "name": "Too Many Arguments (LPL)", + "acronym": "LPL", + "enabled": true, + "smell_description": "Functions with many arguments are harder to optimize and often require more memory and call overhead, increasing CPU load and energy usage.", + "analyzer_options": { + "max_args": { + "label": "Number of Arguments", + "description": "Detecting functions with this many arguments.", + "value": 6 + } + } + }, + "no-self-use": { + "message_id": "R6301", + "name": "No Self Use (NSU)", + "acronym": "NSU", + "enabled": true, + "smell_description": "Methods that don't use 'self' can be static, reducing object overhead and avoiding unnecessary memory binding at runtime.", + "analyzer_options": {} + }, + "long-lambda-expression": { + "message_id": "LLE001", + "name": "Long Lambda Expression (LLE)", + "acronym": "LLE", + "enabled": true, + "smell_description": "Complex lambdas are harder for the interpreter to optimize and may lead to repeated evaluations, which can increase CPU usage and energy draw.", + "analyzer_options": { + "threshold_length": { + "label": "Lambda Length", + "description": "Detects lambda expressions exceeding this length.", + "value": 9 + }, + "threshold_count": { + "label": "Repetition Count", + "description": "Flags patterns that repeat at least this many times.", + "value": 5 + } + } + }, + "long-message-chain": { + "message_id": "LMC001", + "name": "Long Message Chain (LMC)", + "acronym": "LMC", + "enabled": true, + "smell_description": "Deeply nested calls create performance bottlenecks due to increased dereferencing and lookup time, which adds to CPU cycles and energy usage.", + "analyzer_options": { + "threshold": { + "label": "Threshold", + "description": "Defines a threshold for triggering this smell.", + "value": 9 + } + } + }, + "long-element-chain": { + "message_id": "LEC001", + "name": "Long Element Chain (LEC)", + "acronym": "LEC", + "enabled": true, + "smell_description": "Chained element access can be inefficient in large structures, increasing access time and CPU effort, thereby consuming more energy.", + "analyzer_options": { + "threshold": { + "label": "Threshold", + "description": "Defines a threshold for triggering this smell.", + "value": 3 + } + } + }, + "cached-repeated-calls": { + "message_id": "CRC001", + "name": "Cached Repeated Calls (CRC)", + "acronym": "CRC", + "enabled": true, + "smell_description": "Failing to cache repeated expensive calls leads to redundant computation, which wastes CPU cycles and drains energy needlessly.", + "analyzer_options": { + "threshold": { + "label": "Cache Threshold", + "description": "Number of times a function must repeat before caching.", + "value": 2 + } + } + }, + "string-concat-loop": { + "message_id": "SCL001", + "name": "String Concatenation in Loops (SCL)", + "acronym": "SCL", + "enabled": true, + "smell_description": "String concatenation in loops creates new objects each time, increasing memory churn and CPU workload, which leads to higher energy consumption.", + "analyzer_options": {} + } +} diff --git a/data/working_smells_config.json b/data/working_smells_config.json new file mode 100644 index 0000000..4c7e3df --- /dev/null +++ b/data/working_smells_config.json @@ -0,0 +1,101 @@ +{ + "use-a-generator": { + "message_id": "R1729", + "name": "Use A Generator (UGEN)", + "acronym": "UGEN", + "enabled": true, + "smell_description": "Using generators instead of lists reduces memory consumption and avoids unnecessary allocations, leading to more efficient CPU and energy use.", + "analyzer_options": {} + }, + "too-many-arguments": { + "message_id": "R0913", + "name": "Too Many Arguments (LPL)", + "acronym": "LPL", + "enabled": true, + "smell_description": "Functions with many arguments are harder to optimize and often require more memory and call overhead, increasing CPU load and energy usage.", + "analyzer_options": { + "max_args": { + "label": "Number of Arguments", + "description": "Detecting functions with this many arguments.", + "value": 6 + } + } + }, + "no-self-use": { + "message_id": "R6301", + "name": "No Self Use (NSU)", + "acronym": "NSU", + "enabled": true, + "smell_description": "Methods that don't use 'self' can be static, reducing object overhead and avoiding unnecessary memory binding at runtime.", + "analyzer_options": {} + }, + "long-lambda-expression": { + "message_id": "LLE001", + "name": "Long Lambda Expression (LLE)", + "acronym": "LLE", + "enabled": true, + "smell_description": "Complex lambdas are harder for the interpreter to optimize and may lead to repeated evaluations, which can increase CPU usage and energy draw.", + "analyzer_options": { + "threshold_length": { + "label": "Lambda Length", + "description": "Detects lambda expressions exceeding this length.", + "value": 9 + }, + "threshold_count": { + "label": "Repetition Count", + "description": "Flags patterns that repeat at least this many times.", + "value": 5 + } + } + }, + "long-message-chain": { + "message_id": "LMC001", + "name": "Long Message Chain (LMC)", + "acronym": "LMC", + "enabled": true, + "smell_description": "Deeply nested calls create performance bottlenecks due to increased dereferencing and lookup time, which adds to CPU cycles and energy usage.", + "analyzer_options": { + "threshold": { + "label": "Threshold", + "description": "Defines a threshold for triggering this smell.", + "value": 9 + } + } + }, + "long-element-chain": { + "message_id": "LEC001", + "name": "Long Element Chain (LEC)", + "acronym": "LEC", + "enabled": true, + "smell_description": "Chained element access can be inefficient in large structures, increasing access time and CPU effort, thereby consuming more energy.", + "analyzer_options": { + "threshold": { + "label": "Threshold", + "description": "Defines a threshold for triggering this smell.", + "value": 3 + } + } + }, + "cached-repeated-calls": { + "message_id": "CRC001", + "name": "Cached Repeated Calls (CRC)", + "acronym": "CRC", + "enabled": true, + "smell_description": "Failing to cache repeated expensive calls leads to redundant computation, which wastes CPU cycles and drains energy needlessly.", + "analyzer_options": { + "threshold": { + "label": "Cache Threshold", + "description": "Number of times a function must repeat before caching.", + "value": 2 + } + } + }, + "string-concat-loop": { + "message_id": "SCL001", + "name": "String Concatenation in Loops (SCL)", + "acronym": "SCL", + "enabled": true, + "smell_description": "String concatenation in loops creates new objects each time, increasing memory churn and CPU workload, which leads to higher energy consumption.", + "analyzer_options": {} + } +} diff --git a/eslint.config.mjs b/eslint.config.mjs index d5c0b53..010c150 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,28 +1,63 @@ -import typescriptEslint from "@typescript-eslint/eslint-plugin"; -import tsParser from "@typescript-eslint/parser"; +import js from '@eslint/js'; +import tseslint from '@typescript-eslint/eslint-plugin'; +import tsParser from '@typescript-eslint/parser'; +import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; +import eslintConfigPrettier from 'eslint-config-prettier'; +import unusedImports from 'eslint-plugin-unused-imports'; -export default [{ - files: ["**/*.ts"], -}, { +export default [ + { + ignores: [ + '**/*.mjs', + '**/node_modules/**', + '**/dist/**', + '**/coverage/**', + '**/.vscode/**', + '**/media/**', + '**/.husky/**', + '**/*.config.js', + ], + }, + js.configs.recommended, + { + files: ['**/*.ts'], plugins: { - "@typescript-eslint": typescriptEslint, + '@typescript-eslint': tseslint, + 'unused-imports': unusedImports, }, - languageOptions: { - parser: tsParser, - ecmaVersion: 2022, - sourceType: "module", + parser: tsParser, + ecmaVersion: 2022, + sourceType: 'module', }, - rules: { - "@typescript-eslint/naming-convention": ["warn", { - selector: "import", - format: ["camelCase", "PascalCase"], - }], - - curly: "warn", - eqeqeq: "warn", - "no-throw-literal": "warn", - semi: "warn", + 'no-undef': 'off', + 'no-unused-vars': 'off', + 'prettier/prettier': 'error', + '@typescript-eslint/explicit-function-return-type': 'warn', + '@typescript-eslint/naming-convention': [ + 'warn', + { + selector: 'import', + format: ['camelCase', 'PascalCase'], + }, + ], + curly: 'warn', + eqeqeq: 'warn', + 'no-throw-literal': 'warn', + semi: 'warn', + 'unused-imports/no-unused-imports': 'error', + 'unused-imports/no-unused-vars': [ + 'warn', + { + vars: 'all', + varsIgnorePattern: '^_', + args: 'after-used', + argsIgnorePattern: '^_', + }, + ], }, -}]; \ No newline at end of file + }, + eslintPluginPrettierRecommended, + eslintConfigPrettier, +]; diff --git a/package-lock.json b/package-lock.json index a87bfd8..8dfc29b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,1334 +1,2913 @@ { - "name": "ecooptimizer-vs-code-plugin", - "version": "0.0.1", + "name": "ecooptimizer", + "version": "0.2.2", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "ecooptimizer-vs-code-plugin", - "version": "0.0.1", + "name": "ecooptimizer", + "version": "0.2.2", + "dependencies": { + "@types/dotenv": "^6.1.1", + "bufferutil": "^4.0.9", + "dotenv": "^16.4.7", + "dotenv-webpack": "^8.1.0", + "utf-8-validate": "^6.0.5", + "ws": "^8.18.0" + }, "devDependencies": { - "@types/mocha": "^10.0.10", + "@types/adm-zip": "^0.5.7", + "@types/jest": "^29.5.14", "@types/node": "20.x", "@types/vscode": "^1.92.0", - "@typescript-eslint/eslint-plugin": "^8.17.0", - "@typescript-eslint/parser": "^8.17.0", + "@types/ws": "^8.5.14", + "@typescript-eslint/eslint-plugin": "^8.24.1", + "@typescript-eslint/parser": "^8.24.1", "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.4.1", - "eslint": "^9.16.0", + "css-loader": "^7.1.2", + "eslint": "^9.21.0", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-prettier": "^5.2.3", + "eslint-plugin-unused-imports": "^4.1.4", + "husky": "^9.1.7", + "jest": "^29.7.0", + "jest-silent-reporter": "^0.6.0", + "lint-staged": "^15.4.3", + "prettier": "^3.5.2", + "prettier-plugin-tailwindcss": "^0.6.11", + "style-loader": "^4.0.0", + "ts-jest": "^29.2.6", "ts-loader": "^9.5.1", "typescript": "^5.7.2", "webpack": "^5.95.0", - "webpack-cli": "^5.1.4" + "webpack-cli": "^5.1.4", + "webpack-node-externals": "^3.0.0" }, "engines": { "vscode": "^1.92.0" } }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, - "license": "MIT" + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "dev": true, - "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, "engines": { - "node": ">=10.0.0" + "node": ">=6.9.0" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", - "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "node_modules/@babel/compat-data": { + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", + "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.9.tgz", + "integrity": "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==", "dev": true, - "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.4.3" + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.9", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.9", + "@babel/parser": "^7.26.9", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.9", + "@babel/types": "^7.26.9", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=6.9.0" }, "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "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==", + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@eslint/config-array": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", - "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", + "node_modules/@babel/generator": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz", + "integrity": "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.5", - "debug": "^4.3.1", - "minimatch": "^3.1.2" + "@babel/parser": "^7.26.9", + "@babel/types": "^7.26.9", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" } }, - "node_modules/@eslint/config-array/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==", + "node_modules/@babel/helper-compilation-targets": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", + "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", "dev": true, - "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@babel/compat-data": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, - "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" + "yallist": "^3.0.2" } }, - "node_modules/@eslint/core": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", - "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "@types/json-schema": "^7.0.15" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.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==", + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", "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" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" }, - "funding": { - "url": "https://opencollective.com/eslint" + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@eslint/eslintrc/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==", + "node_modules/@babel/helper-plugin-utils": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, "engines": { - "node": "*" + "node": ">=6.9.0" } }, - "node_modules/@eslint/js": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz", - "integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==", + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "dev": true, - "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" } }, - "node_modules/@eslint/object-schema": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", - "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", "dev": true, - "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" } }, - "node_modules/@eslint/plugin-kit": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", - "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", + "node_modules/@babel/helpers": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz", + "integrity": "sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@eslint/core": "^0.10.0", - "levn": "^0.4.1" + "@babel/template": "^7.26.9", + "@babel/types": "^7.26.10" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.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==", + "node_modules/@babel/parser": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz", + "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==", "dev": true, - "license": "Apache-2.0", + "dependencies": { + "@babel/types": "^7.26.9" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, "engines": { - "node": ">=18.18.0" + "node": ">=6.0.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==", + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=18.18.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@humanfs/node/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==", + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "peerDependencies": { + "@babel/core": "^7.0.0-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==", + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", - "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", "dev": true, - "license": "Apache-2.0", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, "engines": { - "node": ">=18.18" + "node": ">=6.9.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", + "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", "dev": true, - "license": "ISC", "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { - "node": ">=12" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, - "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=6.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", "dev": true, - "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, "engines": { - "node": ">=6.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "dev": true, - "license": "MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, - "license": "MIT" + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, - "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "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==", + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, - "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">= 8" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "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==", + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "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==", + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", "dev": true, - "license": "MIT", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">= 8" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, - "license": "MIT", - "optional": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, "engines": { - "node": ">=14" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@types/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", + "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", "dev": true, - "license": "MIT", "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "node_modules/@babel/template": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", + "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", "dev": true, - "license": "MIT", "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.26.9", + "@babel/types": "^7.26.9" + }, + "engines": { + "node": ">=6.9.0" } }, - "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==", + "node_modules/@babel/traverse": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.9.tgz", + "integrity": "sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==", "dev": true, - "license": "MIT" + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.9", + "@babel/parser": "^7.26.9", + "@babel/template": "^7.26.9", + "@babel/types": "^7.26.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true, - "license": "MIT" + "engines": { + "node": ">=4" + } }, - "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==", + "node_modules/@babel/types": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz", + "integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } }, - "node_modules/@types/mocha": { - "version": "10.0.10", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", - "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true, "license": "MIT" }, - "node_modules/@types/node": { - "version": "20.17.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.14.tgz", - "integrity": "sha512-w6qdYetNL5KRBiSClK/KWai+2IMEJuAj+EujKCumalFOwXtvOXaEan9AuwcRID2IcOIAWSIfR495hBtgKlx2zg==", + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, - "license": "MIT", + "optional": true, + "peer": true, "dependencies": { - "undici-types": "~6.19.2" + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" } }, - "node_modules/@types/vscode": { - "version": "1.96.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.96.0.tgz", - "integrity": "sha512-qvZbSZo+K4ZYmmDuaodMbAa67Pl6VDQzLKFka6rq+3WUTY4Kro7Bwoi0CuZLO/wema0ygcmpwow7zZfPJTs5jg==", + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, - "license": "MIT" + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.20.0.tgz", - "integrity": "sha512-naduuphVw5StFfqp4Gq4WhIBE2gN1GEmMUExpJYknZJdRnc+2gDzB8Z3+5+/Kv33hPQRDGzQO/0opHE72lZZ6A==", + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", "dev": true, "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.20.0", - "@typescript-eslint/type-utils": "8.20.0", - "@typescript-eslint/utils": "8.20.0", - "@typescript-eslint/visitor-keys": "8.20.0", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.0.0" - }, "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.8.0" + "node": ">=10.0.0" } }, - "node_modules/@typescript-eslint/parser": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.20.0.tgz", - "integrity": "sha512-gKXG7A5HMyjDIedBi6bUrDcun8GIjnI8qOwVLiY3rx6T/sHP/19XLJOnIq/FgQvWLHja5JN/LSE7eklNBr612g==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.20.0", - "@typescript-eslint/types": "8.20.0", - "@typescript-eslint/typescript-estree": "8.20.0", - "@typescript-eslint/visitor-keys": "8.20.0", - "debug": "^4.3.4" + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://opencollective.com/eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.20.0.tgz", - "integrity": "sha512-J7+VkpeGzhOt3FeG1+SzhiMj9NzGD/M6KoGn9f4dbz3YzK9hvbhVTmLj/HiTp9DazIzJ8B4XcM80LrR9Dm1rJw==", + "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", - "dependencies": { - "@typescript-eslint/types": "8.20.0", - "@typescript-eslint/visitor-keys": "8.20.0" - }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.20.0.tgz", - "integrity": "sha512-bPC+j71GGvA7rVNAHAtOjbVXbLN5PkwqMvy1cwGeaxUoRQXVuKCebRoLzm+IPW/NtFFpstn1ummSIasD5t60GA==", + "node_modules/@eslint/config-array": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", + "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.20.0", - "@typescript-eslint/utils": "8.20.0", - "debug": "^4.3.4", - "ts-api-utils": "^2.0.0" + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" }, "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.8.0" } }, - "node_modules/@typescript-eslint/types": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.20.0.tgz", - "integrity": "sha512-cqaMiY72CkP+2xZRrFt3ExRBu0WmVitN/rYPZErA80mHjHx/Svgp8yfbzkJmDoQ/whcytOPO9/IZXnOc+wigRA==", + "node_modules/@eslint/config-array/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==", "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.20.0.tgz", - "integrity": "sha512-Y7ncuy78bJqHI35NwzWol8E0X7XkRVS4K4P4TCyzWkOJih5NDvtoRDW4Ba9YJJoB2igm9yXDdYI/+fkiiAxPzA==", + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.20.0", - "@typescript-eslint/visitor-keys": "8.20.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.0" + "brace-expansion": "^1.1.7" }, "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.8.0" + "node": "*" } }, - "node_modules/@typescript-eslint/utils": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.20.0.tgz", - "integrity": "sha512-dq70RUw6UK9ei7vxc4KQtBRk7qkHZv447OUZ6RPQMQl71I3NZxQJX/f32Smr+iqWrB02pHKn2yAdHBb0KNrRMA==", + "node_modules/@eslint/core": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", + "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", "dev": true, - "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.20.0", - "@typescript-eslint/types": "8.20.0", - "@typescript-eslint/typescript-estree": "8.20.0" + "@types/json-schema": "^7.0.15" }, "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.8.0" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.20.0.tgz", - "integrity": "sha512-v/BpkeeYAsPkKCkR8BDwcno0llhzWVqPOamQrAEMdpZav2Y9OVjd9dwJyBLJWwf335B5DmlifECIkZRJCaGaHA==", + "node_modules/@eslint/eslintrc": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.0.tgz", + "integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.20.0", - "eslint-visitor-keys": "^4.2.0" + "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": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://opencollective.com/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==", + "node_modules/@eslint/eslintrc/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==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@vscode/test-cli": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/@vscode/test-cli/-/test-cli-0.0.10.tgz", - "integrity": "sha512-B0mMH4ia+MOOtwNiLi79XhA+MLmUItIC8FckEuKrVAVriIuSWjt7vv4+bF8qVFiNFe4QRfzPaIZk39FZGWEwHA==", + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "MIT", "dependencies": { - "@types/mocha": "^10.0.2", - "c8": "^9.1.0", - "chokidar": "^3.5.3", - "enhanced-resolve": "^5.15.0", - "glob": "^10.3.10", - "minimatch": "^9.0.3", - "mocha": "^10.2.0", - "supports-color": "^9.4.0", - "yargs": "^17.7.2" - }, - "bin": { - "vscode-test": "out/bin.mjs" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=18" + "node": "*" } }, - "node_modules/@vscode/test-electron": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.4.1.tgz", - "integrity": "sha512-Gc6EdaLANdktQ1t+zozoBVRynfIsMKMc94Svu1QreOBC8y76x4tvaK32TljrLi1LI2+PK58sDVbL7ALdqf3VRQ==", + "node_modules/@eslint/js": { + "version": "9.21.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.21.0.tgz", + "integrity": "sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw==", "dev": true, - "license": "MIT", - "dependencies": { - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.5", - "jszip": "^3.10.1", - "ora": "^7.0.1", - "semver": "^7.6.2" - }, "engines": { - "node": ">=16" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@webassemblyjs/ast": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", + "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", "dev": true, - "license": "MIT", "dependencies": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + "@eslint/core": "^0.12.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "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": "MIT" + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "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": "MIT" + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "node_modules/@humanfs/node/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": "MIT" + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "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, - "license": "MIT", - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", - "@xtuc/long": "4.2.2" + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "node_modules/@humanwhocodes/retry": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", + "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", "dev": true, - "license": "MIT" + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", - "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/wasm-gen": "1.14.1" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" } }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", - "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, - "license": "MIT", "dependencies": { - "@xtuc/ieee754": "^1.2.0" + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", - "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "@xtuc/long": "4.2.2" + "sprintf-js": "~1.0.2" } }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, - "license": "MIT" + "engines": { + "node": ">=6" + } }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", - "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, - "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/helper-wasm-section": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-opt": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1", - "@webassemblyjs/wast-printer": "1.14.1" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, - "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", - "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, - "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1" + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", - "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, - "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-api-error": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", - "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, - "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@xtuc/long": "4.2.2" + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@webpack-cli/configtest": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", - "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, - "license": "MIT", "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" + "node": ">=8" } }, - "node_modules/@webpack-cli/info": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", - "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, "license": "MIT", "engines": { - "node": ">=14.15.0" + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@webpack-cli/serve": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", - "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dev": true, - "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, "engines": { - "node": ">=14.15.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "peerDependenciesMeta": { - "webpack-dev-server": { + "node-notifier": { "optional": true } } }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "node_modules/@jest/core/node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, - "license": "BSD-3-Clause" + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "node_modules/@jest/core/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==", "dev": true, - "license": "Apache-2.0" + "engines": { + "node": ">=8" + } }, - "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "node_modules/@jest/core/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==", "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" + "dependencies": { + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=0.4.0" + "node": ">=8" } }, - "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==", + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", "dev": true, - "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, "engines": { - "node": ">= 14" + "node": "^14.15.0 || ^16.10.0 || >=18.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==", + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "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" + "jest-get-type": "^29.6.3" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "dev": true, - "license": "MIT", "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "node_modules/@jest/fake-timers/node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, - "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "@sinonjs/commons": "^3.0.0" } }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, - "license": "MIT" + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "dev": true, - "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, "peerDependencies": { - "ajv": "^6.9.1" + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "node_modules/@jest/reporters/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==", "dev": true, - "license": "MIT", "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "node_modules/@jest/reporters/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==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "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==", + "node_modules/@jest/reporters/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", "dev": true, - "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "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": ">=8" + "node": "*" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "node_modules/@jest/reporters/node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, - "license": "ISC", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, "engines": { - "node": ">= 8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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/@jest/reporters/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "MIT" + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "node_modules/@jest/reporters/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==", "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": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "node_modules/@jest/reporters/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, - "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/bl": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", - "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, - "license": "MIT", "dependencies": { - "buffer": "^6.0.3", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", "dev": true, - "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" }, "engines": { - "node": ">= 6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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==", + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "dev": true, - "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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==", + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", "dev": true, - "license": "MIT", "dependencies": { - "fill-range": "^7.1.1" + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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==", - "dev": true, - "license": "ISC" - }, - "node_modules/browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" - }, - "bin": { - "browserslist": "cli.js" + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" }, "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "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": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/c8": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/c8/-/c8-9.1.0.tgz", - "integrity": "sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg==", - "dev": true, - "license": "ISC", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "license": "MIT", "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@istanbuljs/schema": "^0.1.3", - "find-up": "^5.0.0", - "foreground-child": "^3.1.1", - "istanbul-lib-coverage": "^3.2.0", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.1.6", - "test-exclude": "^6.0.0", - "v8-to-istanbul": "^9.0.0", - "yargs": "^17.7.2", - "yargs-parser": "^21.1.1" - }, - "bin": { - "c8": "bin/c8.js" + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { - "node": ">=14.14.0" + "node": ">=6.0.0" } }, - "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, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "license": "MIT", "engines": { - "node": ">=6" + "node": ">=6.0.0" } }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6.0.0" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001692", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001692.tgz", - "integrity": "sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "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, + "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, + "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, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@types/adm-zip": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.7.tgz", + "integrity": "sha512-DNEs/QvmyRLurdQPChqq0Md4zGvPwHerAJYWk9l2jCbD1VPpnzRJorOdiq4zsw09NFbYnhfsoEhWtxIzXpn2yw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/dotenv": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@types/dotenv/-/dotenv-6.1.1.tgz", + "integrity": "sha512-ftQl3DtBvqHl9L16tpqqzA4YzCSXZfi7g8cQceTz5rOlYtk/IZbFjAv3mLOQlNIgOaylCQWQoBdDQHPgEBJPHg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/@types/mocha": { + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.17.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.14.tgz", + "integrity": "sha512-w6qdYetNL5KRBiSClK/KWai+2IMEJuAj+EujKCumalFOwXtvOXaEan9AuwcRID2IcOIAWSIfR495hBtgKlx2zg==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true + }, + "node_modules/@types/vscode": { + "version": "1.96.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.96.0.tgz", + "integrity": "sha512-qvZbSZo+K4ZYmmDuaodMbAa67Pl6VDQzLKFka6rq+3WUTY4Kro7Bwoi0CuZLO/wema0ygcmpwow7zZfPJTs5jg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.14.tgz", + "integrity": "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.24.1.tgz", + "integrity": "sha512-ll1StnKtBigWIGqvYDVuDmXJHVH4zLVot1yQ4fJtLpL7qacwkxJc1T0bptqw+miBQ/QfUbhl1TcQ4accW5KUyA==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.24.1", + "@typescript-eslint/type-utils": "8.24.1", + "@typescript-eslint/utils": "8.24.1", + "@typescript-eslint/visitor-keys": "8.24.1", + "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.8.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.24.1.tgz", + "integrity": "sha512-Tqoa05bu+t5s8CTZFaGpCH2ub3QeT9YDkXbPd3uQ4SfsLoh1/vv2GEYAioPoxCWJJNsenXlC88tRjwoHNts1oQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.24.1", + "@typescript-eslint/types": "8.24.1", + "@typescript-eslint/typescript-estree": "8.24.1", + "@typescript-eslint/visitor-keys": "8.24.1", + "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.8.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.24.1.tgz", + "integrity": "sha512-OdQr6BNBzwRjNEXMQyaGyZzgg7wzjYKfX2ZBV3E04hUCBDv3GQCHiz9RpqdUIiVrMgJGkXm3tcEh4vFSHreS2Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.24.1", + "@typescript-eslint/visitor-keys": "8.24.1" + }, + "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.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.24.1.tgz", + "integrity": "sha512-/Do9fmNgCsQ+K4rCz0STI7lYB4phTtEXqqCAs3gZW0pnK7lWNkvWd5iW545GSmApm4AzmQXmSqXPO565B4WVrw==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "8.24.1", + "@typescript-eslint/utils": "8.24.1", + "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.8.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.24.1.tgz", + "integrity": "sha512-9kqJ+2DkUXiuhoiYIUvIYjGcwle8pcPpdlfkemGvTObzgmYfJ5d0Qm6jwb4NBXP9W1I5tss0VIAnWFumz3mC5A==", + "dev": true, + "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.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.24.1.tgz", + "integrity": "sha512-UPyy4MJ/0RE648DSKQe9g0VDSehPINiejjA6ElqnFaFIhI6ZEiZAkUI0D5MCk0bQcTf/LVqZStvQ6K4lPn/BRg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.24.1", + "@typescript-eslint/visitor-keys": "8.24.1", + "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.8.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.24.1.tgz", + "integrity": "sha512-OOcg3PMMQx9EXspId5iktsI3eMaXVwlhC8BvNnX6B5w9a4dVgpkQZuU8Hy67TolKcl+iFWq0XX+jbDGN4xWxjQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.24.1", + "@typescript-eslint/types": "8.24.1", + "@typescript-eslint/typescript-estree": "8.24.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.8.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.24.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.24.1.tgz", + "integrity": "sha512-EwVHlp5l+2vp8CoqJm9KikPZgi3gbdZAtabKT9KPShGeOcJhsv4Zdo3oc8T8I0uKEmYoU4ItyxbptjF08enaxg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.24.1", + "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, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vscode/test-cli": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/@vscode/test-cli/-/test-cli-0.0.10.tgz", + "integrity": "sha512-B0mMH4ia+MOOtwNiLi79XhA+MLmUItIC8FckEuKrVAVriIuSWjt7vv4+bF8qVFiNFe4QRfzPaIZk39FZGWEwHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mocha": "^10.0.2", + "c8": "^9.1.0", + "chokidar": "^3.5.3", + "enhanced-resolve": "^5.15.0", + "glob": "^10.3.10", + "minimatch": "^9.0.3", + "mocha": "^10.2.0", + "supports-color": "^9.4.0", + "yargs": "^17.7.2" + }, + "bin": { + "vscode-test": "out/bin.mjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vscode/test-cli/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@vscode/test-cli/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==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/@vscode/test-cli/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vscode/test-cli/node_modules/mocha": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@vscode/test-cli/node_modules/mocha/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vscode/test-cli/node_modules/mocha/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@vscode/test-cli/node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/@vscode/test-cli/node_modules/mocha/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "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/@vscode/test-cli/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==", + "dev": true, + "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/@vscode/test-cli/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@vscode/test-cli/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==", + "dev": true, + "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/@vscode/test-cli/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==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/@vscode/test-electron": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.4.1.tgz", + "integrity": "sha512-Gc6EdaLANdktQ1t+zozoBVRynfIsMKMc94Svu1QreOBC8y76x4tvaK32TljrLi1LI2+PK58sDVbL7ALdqf3VRQ==", + "dev": true, + "dependencies": { + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "jszip": "^3.10.1", + "ora": "^7.0.1", + "semver": "^7.6.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "license": "Apache-2.0" + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "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/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "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/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", + "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", + "dev": true, + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "optional": true, + "peer": true + }, + "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/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.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==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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/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==", + "dev": true, + "license": "ISC" + }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "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": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "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/bufferutil": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.9.tgz", + "integrity": "sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/c8": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/c8/-/c8-9.1.0.tgz", + "integrity": "sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^6.0.0", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": ">=14.14.0" + } + }, + "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, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001692", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001692.tgz", + "integrity": "sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" }, "node_modules/chalk": { "version": "4.1.2", @@ -1337,1285 +2916,2780 @@ "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/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==", + "dev": true, + "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/cliui/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/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==", + "dev": true, + "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/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "optional": true, + "peer": true + }, + "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/css-loader": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", + "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "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, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-defaults": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dotenv-defaults/-/dotenv-defaults-2.0.2.tgz", + "integrity": "sha512-iOIzovWfsUHU91L5i8bJce3NYK5JXeAwH50Jh6+ARUdLiiGlYWfGw6UkzsYqaXZH/hjE/eCd/PlfM/qqyK0AMg==", + "dependencies": { + "dotenv": "^8.2.0" + } + }, + "node_modules/dotenv-defaults/node_modules/dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "engines": { + "node": ">=10" + } + }, + "node_modules/dotenv-webpack": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/dotenv-webpack/-/dotenv-webpack-8.1.0.tgz", + "integrity": "sha512-owK1JcsPkIobeqjVrk6h7jPED/W6ZpdFsMPR+5ursB7/SdgDyO+VzAU+szK8C8u3qUhtENyYnj8eyXMR5kkGag==", + "dependencies": { + "dotenv-defaults": "^2.0.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "webpack": "^4 || ^5" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.83", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.83.tgz", + "integrity": "sha512-LcUDPqSt+V0QmI47XLzZrz5OqILSMGsPFkDYus22rIbgorSvBYEFqq854ltTmUdHkY92FSdAAvsh4jWEULMdfQ==", + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", + "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/envinfo": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", + "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", + "dev": true, + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-module-lexer": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", + "license": "MIT" + }, + "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", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.21.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.21.0.tgz", + "integrity": "sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.2", + "@eslint/core": "^0.12.0", + "@eslint/eslintrc": "^3.3.0", + "@eslint/js": "9.21.0", + "@eslint/plugin-kit": "^0.2.7", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.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" + }, + "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-config-prettier": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.0.1.tgz", + "integrity": "sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw==", + "dev": true, + "bin": { + "eslint-config-prettier": "build/bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.3.tgz", + "integrity": "sha512-qJ+y0FfCp/mQYQ/vWQ3s7eUlFEL4PyKfAJxsnYTJ4YT73nsJBWqmEpFryxV9OeUiqmsTsYJ5Y+KDNaeP31wrRw==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.9.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-unused-imports": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.1.4.tgz", + "integrity": "sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ==", + "dev": true, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0", + "eslint": "^9.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "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, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "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/eslint/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, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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, + "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, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "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==", + "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==", + "license": "BSD-2-Clause", + "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, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.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", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "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, + "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-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==", + "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, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.5.tgz", + "integrity": "sha512-5JnBCWpFlMo0a3ciDy/JckMzzv1U9coZrIhedq+HXxxUfDTAiS0LA8OKVao4G9BxmCVck/jtA5r3KAtRWEyD8Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz", + "integrity": "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "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, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "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, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/chalk/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==", + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "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, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "flatted": "^3.2.9", + "keyv": "^4.5.4" }, "engines": { - "node": ">=8" + "node": ">=16" } }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "node_modules/flatted": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", + "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "dev": true, + "license": "ISC" + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "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==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/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": ">= 8.10.0" + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "license": "BSD-2-Clause" + }, + "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, + "engines": { + "node": ">=18" }, "funding": { - "url": "https://paulmillr.com/funding/" + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "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/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "engines": { + "node": ">= 0.4" } }, - "node_modules/chrome-trace-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, "engines": { - "node": ">=6.0" + "node": ">= 14" } }, - "node_modules/cli-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "license": "MIT", "dependencies": { - "restore-cursor": "^4.0.0" + "agent-base": "^7.1.2", + "debug": "4" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 14" } }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", "dev": true, - "license": "MIT", "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=16.17.0" } }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "bin": { + "husky": "bin.js" }, "engines": { - "node": ">=12" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" } }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", "dev": true, - "license": "MIT", "engines": { - "node": ">=8" + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" } }, - "node_modules/cliui/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==", + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "dev": true, - "license": "MIT" + "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": "BSD-3-Clause" }, - "node_modules/cliui/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==", + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, "engines": { - "node": ">=8" + "node": ">= 4" } }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/cliui/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==", + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, - "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=6" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", "dev": true, "license": "MIT", "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" }, "engines": { - "node": ">=6" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, "engines": { - "node": ">=7.0.0" + "node": ">=0.8.19" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "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.", "dev": true, - "license": "MIT" + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true, - "license": "MIT" + "license": "ISC" }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } }, - "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==", - "dev": true, - "license": "MIT" + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "license": "MIT", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "binary-extensions": "^2.0.0" }, "engines": { - "node": ">= 8" + "node": ">=8" } }, - "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "node_modules/is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", "dev": true, - "license": "MIT", "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" + "ci-info": "^2.0.0" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "bin": { + "is-ci": "bin.js" } - }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + }, + "node_modules/is-ci/node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "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, - "license": "MIT" - }, - "node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "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, - "license": "BSD-3-Clause", + "license": "MIT", "engines": { - "node": ">=0.3.1" + "node": ">=0.10.0" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.83", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.83.tgz", - "integrity": "sha512-LcUDPqSt+V0QmI47XLzZrz5OqILSMGsPFkDYus22rIbgorSvBYEFqq854ltTmUdHkY92FSdAAvsh4jWEULMdfQ==", + "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==", "dev": true, - "license": "ISC" + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, - "license": "MIT" + "engines": { + "node": ">=6" + } }, - "node_modules/enhanced-resolve": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", - "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==", + "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, "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">=10.13.0" + "node": ">=0.10.0" } }, - "node_modules/envinfo": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", - "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", "dev": true, "license": "MIT", - "bin": { - "envinfo": "dist/cli.js" - }, "engines": { - "node": ">=4" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/es-module-lexer": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", - "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "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": ">=6" + "node": ">=0.12.0" } }, - "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==", + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, - "node_modules/eslint": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz", - "integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==", + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.10.0", - "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.18.0", - "@eslint/plugin-kit": "^0.2.5", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.1", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.2.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.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" - }, - "bin": { - "eslint": "bin/eslint.js" + "isobject": "^3.0.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } + "node": ">=0.10.0" } }, - "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==", + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", "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" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=10" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/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==", + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } + "license": "MIT" }, - "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==", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } + "license": "ISC" }, - "node_modules/eslint/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==", + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, + "license": "MIT", "engines": { - "node": ">=10.13.0" + "node": ">=0.10.0" } }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "license": "BSD-3-Clause", "engines": { - "node": "*" + "node": ">=8" } }, - "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", "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" + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" }, - "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": ">=10" } }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "estraverse": "^5.1.0" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=0.10" + "node": ">=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==", + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "estraverse": "^5.2.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=4.0" + "node": ">=8" } }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, - "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, "engines": { - "node": ">=4.0" + "node": ">=10" } }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "node_modules/istanbul-lib-source-maps/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==", "dev": true, - "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, "engines": { - "node": ">=0.8.x" + "node": ">=8" } }, - "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==", + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "dependencies": { - "@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" + "@isaacs/cliui": "^8.0.2" }, - "engines": { - "node": ">=8.6.0" + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, - "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, - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.5.tgz", - "integrity": "sha512-5JnBCWpFlMo0a3ciDy/JckMzzv1U9coZrIhedq+HXxxUfDTAiS0LA8OKVao4G9BxmCVck/jtA5r3KAtRWEyD8Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", "dev": true, - "license": "MIT", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, "engines": { - "node": ">= 4.9.1" + "node": ">=10" } }, - "node_modules/fastq": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", - "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", + "node_modules/jake/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==", "dev": true, - "license": "ISC", "dependencies": { - "reusify": "^1.0.4" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "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==", + "node_modules/jake/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "MIT", "dependencies": { - "flat-cache": "^4.0.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=16.0.0" + "node": "*" } }, - "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==", + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, - "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "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==", + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", "dev": true, - "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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==", + "node_modules/jest-changed-files/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, - "license": "MIT", "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" }, "engines": { - "node": ">=16" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/flatted": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", - "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", - "dev": true, - "license": "ISC" - }, - "node_modules/foreground-child": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", - "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "node_modules/jest-changed-files/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, "engines": { - "node": ">=14" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/jest-changed-files/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, - "license": "ISC" + "engines": { + "node": ">=10.17.0" + } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/jest-changed-files/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "node_modules/jest-changed-files/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "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==", + "node_modules/jest-changed-files/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/jest-changed-files/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, - "license": "ISC", "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">=6" } }, - "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, - "license": "ISC", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" }, "bin": { - "glob": "dist/esm/bin.mjs" + "jest": "bin/jest.js" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "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==", + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, - "license": "ISC", "dependencies": { - "is-glob": "^4.0.1" + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": ">= 6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } } }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "node_modules/jest-config/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==", "dev": true, - "license": "BSD-2-Clause" + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } }, - "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "node_modules/jest-config/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", "dev": true, - "license": "MIT", + "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": ">=18" + "node": "*" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "node_modules/jest-config/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "ISC" + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, - "license": "MIT" + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "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==", + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", "dev": true, - "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, - "license": "MIT", "dependencies": { - "function-bind": "^1.1.2" + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, - "license": "MIT" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, - "license": "MIT", "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" }, "engines": { - "node": ">= 14" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" } }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "node_modules/jest-haste-map/node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, - "license": "MIT", "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, "engines": { - "node": ">= 14" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "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": "BSD-3-Clause" - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "node_modules/jest-haste-map/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, - "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": ">= 4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, - "license": "MIT" + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "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==", + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, - "license": "MIT", "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, - "license": "MIT", "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, - "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, "engines": { - "node": ">=0.8.19" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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.", + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, - "license": "ISC" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/interpret": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", - "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, - "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, "engines": { - "node": ">=10.13.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, - "license": "MIT", "dependencies": { - "binary-extensions": "^2.0.0" + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", "dev": true, - "license": "MIT", "dependencies": { - "hasown": "^2.0.2" + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/jest-runner/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==", "dev": true, - "license": "MIT", "engines": { "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==", + "node_modules/jest-runner/node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, - "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/jest-runner/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "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==", + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", "dev": true, - "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-interactive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "node_modules/jest-runtime/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==", "dev": true, - "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/jest-runtime/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", + "dev": true, + "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": ">=12" + "node": "*" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "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==", + "node_modules/jest-runtime/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "MIT", + "dependencies": { + "brace-expansion": "^1.1.7" + }, "engines": { - "node": ">=0.12.0" + "node": "*" } }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "node_modules/jest-runtime/node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "node_modules/jest-silent-reporter": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/jest-silent-reporter/-/jest-silent-reporter-0.6.0.tgz", + "integrity": "sha512-4nmS+5o7ycVlvbQOTx7CnGdbBtP2646hnDgQpQLaVhjHcQNHD+gqBAetyjRDlgpZ8+8N82MWI59K+EX2LsVk7g==", "dev": true, - "license": "MIT", "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" + "chalk": "^4.0.0", + "jest-util": "^26.0.0" } }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "node_modules/jest-silent-reporter/node_modules/@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 10.14.2" } }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "node_modules/jest-silent-reporter/node_modules/@types/yargs": { + "version": "15.0.19", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", + "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", "dev": true, - "license": "ISC" + "dependencies": { + "@types/yargs-parser": "*" + } }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "node_modules/jest-silent-reporter/node_modules/jest-util": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", + "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", "dev": true, - "license": "MIT", + "dependencies": { + "@jest/types": "^26.6.2", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 10.14.2" } }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", "dev": true, - "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/istanbul-lib-report/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==", + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, - "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "node_modules/jest-watcher/node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, - "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/cliui": "^8.0.2" + "type-fest": "^0.21.3" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=8" }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -2630,7 +5704,6 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -2642,6 +5715,12 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -2655,6 +5734,18 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -2666,14 +5757,12 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, "license": "MIT" }, "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": { @@ -2683,6 +5772,18 @@ "dev": true, "license": "MIT" }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jszip": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", @@ -2716,6 +5817,24 @@ "node": ">=0.10.0" } }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2740,54 +5859,342 @@ "immediate": "~3.0.5" } }, - "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/lint-staged": { + "version": "15.4.3", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.4.3.tgz", + "integrity": "sha512-FoH1vOeouNh1pw+90S+cnuoFwRfUD9ijY2GKy5h7HS3OR7JVir2N2xrsa0+Twc1B7cW72L+88geG5cW4wIhn7g==", + "dev": true, + "dependencies": { + "chalk": "^5.4.1", + "commander": "^13.1.0", + "debug": "^4.4.0", + "execa": "^8.0.1", + "lilconfig": "^3.1.3", + "listr2": "^8.2.5", + "micromatch": "^4.0.8", + "pidtree": "^0.6.0", + "string-argv": "^0.3.2", + "yaml": "^2.7.0" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/listr2": { + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.5.tgz", + "integrity": "sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ==", + "dev": true, + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true + }, + "node_modules/listr2/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "license": "MIT", + "engines": { + "node": ">=6.11.5" + } + }, + "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, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "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, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "dev": true, + "dependencies": { + "get-east-asian-width": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", "dev": true, - "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, "engines": { - "node": ">=6.11.5" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "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==", + "node_modules/log-update/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, - "license": "MIT", "dependencies": { - "p-locate": "^5.0.0" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "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, - "license": "MIT" - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", "dev": true, - "license": "MIT", "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/lru-cache": { @@ -2813,11 +6220,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, "license": "MIT" }, "node_modules/merge2": { @@ -2825,7 +6246,6 @@ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 8" } @@ -2848,7 +6268,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -2858,7 +6277,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -2877,6 +6295,18 @@ "node": ">=6" } }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -2903,203 +6333,31 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/mocha": { - "version": "10.8.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", - "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.3", - "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", - "debug": "^4.3.5", - "diff": "^5.2.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^8.1.0", - "he": "^1.2.0", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", - "ms": "^2.1.3", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^16.2.0", - "yargs-parser": "^20.2.9", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/mocha/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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/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==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/mocha/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==", + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "license": "MIT" }, - "node_modules/mocha/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/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==", - "dev": true, - "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/mocha/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==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/mocha/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==", - "dev": true, - "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/mocha/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "dev": true, - "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" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" }, "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/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==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -3111,14 +6369,28 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, "license": "MIT" }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, "license": "MIT" }, "node_modules/normalize-path": { @@ -3131,6 +6403,33 @@ "node": ">=0.10.0" } }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -3328,7 +6627,6 @@ "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" }, @@ -3336,6 +6634,24 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3394,7 +6710,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -3410,6 +6725,27 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -3479,6 +6815,112 @@ "node": ">=8" } }, + "node_modules/postcss": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", + "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -3489,6 +6931,137 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.2.tgz", + "integrity": "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.6.11", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.11.tgz", + "integrity": "sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA==", + "dev": true, + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-import-sort": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-multiline-arrays": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", + "prettier-plugin-style-order": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "@zackad/prettier-plugin-twig": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-import-sort": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-multiline-arrays": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-style-order": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?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", @@ -3496,16 +7069,44 @@ "dev": true, "license": "MIT" }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, "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/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -3524,19 +7125,23 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "license": "MIT" + ] }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" } }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -3593,7 +7198,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -3648,11 +7252,19 @@ "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/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/restore-cursor": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", @@ -3682,12 +7294,17 @@ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true, - "license": "MIT", "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -3707,7 +7324,6 @@ "url": "https://feross.org/support" } ], - "license": "MIT", "dependencies": { "queue-microtask": "^1.2.2" } @@ -3716,14 +7332,12 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, "license": "MIT" }, "node_modules/schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.8", @@ -3755,7 +7369,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" @@ -3794,27 +7407,82 @@ "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==", + "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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, - "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", "dev": true, - "license": "ISC", "engines": { - "node": ">=14" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/source-map": { @@ -3827,11 +7495,19 @@ "node": ">= 8" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "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==", - "dev": true, "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", @@ -3842,12 +7518,38 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/stdin-discarder": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", @@ -3874,6 +7576,49 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length/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==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -3978,6 +7723,18 @@ "node": ">=8" } }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -3991,6 +7748,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", + "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", + "dev": true, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.27.0" + } + }, "node_modules/supports-color": { "version": "9.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", @@ -4017,11 +7790,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/synckit": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", + "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -4031,7 +7819,6 @@ "version": "5.37.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz", "integrity": "sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -4050,7 +7837,6 @@ "version": "5.3.11", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz", "integrity": "sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", @@ -4085,7 +7871,6 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -4102,7 +7887,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3" @@ -4115,14 +7899,12 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, "license": "MIT" }, "node_modules/terser-webpack-plugin/node_modules/schema-utils": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", - "dev": true, "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", @@ -4199,6 +7981,12 @@ "node": "*" } }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -4213,11 +8001,10 @@ } }, "node_modules/ts-api-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", - "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", + "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" }, @@ -4225,6 +8012,66 @@ "typescript": ">=4.8.4" } }, + "node_modules/ts-jest": { + "version": "29.2.6", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.6.tgz", + "integrity": "sha512-yTNZVZqc8lSixm+QGVFcPe6+yj7+TWZwIesuOWvfcn4B9bz5x4NDzVCQQjOs7Hfouu36aEqfEbo9Qpo+gq8dDg==", + "dev": true, + "dependencies": { + "bs-logger": "^0.2.6", + "ejs": "^3.1.10", + "fast-json-stable-stringify": "^2.1.0", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.1", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-jest/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, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/ts-loader": { "version": "9.5.2", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.2.tgz", @@ -4246,6 +8093,68 @@ "webpack": "^5.0.0" } }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -4259,6 +8168,28 @@ "node": ">= 0.8.0" } }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typescript": { "version": "5.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", @@ -4277,14 +8208,12 @@ "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/update-browserslist-db": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", - "dev": true, "funding": [ { "type": "opencollective", @@ -4315,12 +8244,23 @@ "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/utf-8-validate": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.5.tgz", + "integrity": "sha512-EYZR+OpIXp9Y1eG1iueg8KRsY8TuT8VNgnanZ0uA3STqhHQTLwbl+WX76/9X5OY12yQubymBpaBSmMPkSTQcKA==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -4328,6 +8268,14 @@ "dev": true, "license": "MIT" }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "optional": true, + "peer": true + }, "node_modules/v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", @@ -4343,11 +8291,19 @@ "node": ">=10.12.0" } }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, "node_modules/watchpack": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", - "dev": true, "license": "MIT", "dependencies": { "glob-to-regexp": "^0.4.1", @@ -4361,7 +8317,6 @@ "version": "5.97.1", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", - "dev": true, "license": "MIT", "dependencies": { "@types/eslint-scope": "^3.7.7", @@ -4475,11 +8430,19 @@ "node": ">=10.0.0" } }, + "node_modules/webpack-node-externals": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz", + "integrity": "sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/webpack-sources": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, "license": "MIT", "engines": { "node": ">=10.13.0" @@ -4489,7 +8452,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", @@ -4503,7 +8465,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -4651,6 +8612,45 @@ "dev": true, "license": "ISC" }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "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/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -4661,6 +8661,24 @@ "node": ">=10" } }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yaml": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", + "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -4751,6 +8769,17 @@ "node": ">=8" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, "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 2e96184..773269b 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,20 @@ { - "name": "ecooptimizer-vs-code-plugin", - "displayName": "ecooptimizer-vs-code-plugin", - "description": "VS Code Plugin for Ecooptimizer Refactoring Tool", - "version": "0.0.1", + "name": "ecooptimizer", + "publisher": "mac-ecooptimizers", + "displayName": "EcoOptimizer VS Code Plugin", + "contributors": [ + "Sevhena Walker", + "Tanveer Brar", + "Ayushi Amin", + "Mya Hussain", + "Nivetha Kuruparan" + ], + "description": "VS Code Plugin for EcoOptimizer Refactoring Tool", + "repository": { + "type": "git", + "url": "https://github.com/ssm-lab/capstone--sco-vs-code-plugin" + }, + "version": "0.2.3", "engines": { "vscode": "^1.92.0" }, @@ -10,53 +22,462 @@ "Other" ], "activationEvents": [ - "onLanguage:python" + "onLanguage:python", + "onStartupFinished" ], "main": "./dist/extension.js", - "contributes": { - "commands": [ - { - "command": "ecooptimizer-vs-code-plugin.detectSmells", - "title": "Detect Smells", - "category": "Eco" - }, - { - "command": "ecooptimizer-vs-code-plugin.refactorSmell", - "title": "Refactor Smell", - "category": "Eco" - } - ], - "keybindings": [ - { - "command": "ecooptimizer-vs-code-plugin.refactorSmell", - "key": "ctrl+shift+r", - "when": "editorTextFocus && resourceExtname == '.py'" - } - ] + "directories": { + "src": "./src", + "test": "./test" }, "scripts": { + "deploy": "vsce publish --yarn", "vscode:prepublish": "npm run package", "compile": "webpack", + "test": "jest --verbose", + "test:watch": "jest --watch --verbose", "watch": "webpack --watch", "package": "webpack --mode production --devtool hidden-source-map", - "compile-tests": "tsc -p . --outDir out", - "watch-tests": "tsc -p . -w --outDir out", - "pretest": "npm run compile-tests && npm run compile && npm run lint", - "lint": "eslint src", - "test": "vscode-test" + "lint": "eslint src" + }, + "jest": { + "preset": "ts-jest", + "testEnvironment": "node", + "setupFilesAfterEnv": [ + "./test/setup.ts" + ], + "moduleNameMapper": { + "^vscode$": "/test/mocks/vscode-mock.ts", + "^@/(.*)$": "/src/$1" + }, + "moduleDirectories": [ + "node_modules", + "src", + "test/__mocks__" + ], + "roots": [ + "/src", + "/test" + ], + "collectCoverage": true, + "coverageReporters": [ + "text", + "html", + "lcov" + ], + "coverageDirectory": "/coverage/", + "coverageThreshold": { + "global": { + "statements": 80 + } + }, + "collectCoverageFrom": [ + "src/**/*.ts", + "!src/**/*.d.ts", + "!src/**/index.ts", + "!test/mocks/*", + "!src/extension.ts", + "!src/context/*", + "!src/providers/*", + "!src/commands/showLogs.ts", + "!src/emitters/serverStatus.ts", + "!src/utils/envConfig.ts", + "!src/lib/*", + "!src/install.ts" + ] + }, + "lint-staged": { + "src/**/*.ts": [ + "eslint --fix", + "prettier --write" + ] }, "devDependencies": { - "@types/vscode": "^1.92.0", - "@types/mocha": "^10.0.10", + "@types/adm-zip": "^0.5.7", + "@types/jest": "^29.5.14", "@types/node": "20.x", - "@typescript-eslint/eslint-plugin": "^8.17.0", - "@typescript-eslint/parser": "^8.17.0", - "eslint": "^9.16.0", - "typescript": "^5.7.2", + "@types/vscode": "^1.92.0", + "@types/ws": "^8.5.14", + "@typescript-eslint/eslint-plugin": "^8.24.1", + "@typescript-eslint/parser": "^8.24.1", + "@vscode/test-cli": "^0.0.10", + "@vscode/test-electron": "^2.4.1", + "css-loader": "^7.1.2", + "eslint": "^9.21.0", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-prettier": "^5.2.3", + "eslint-plugin-unused-imports": "^4.1.4", + "husky": "^9.1.7", + "jest": "^29.7.0", + "jest-silent-reporter": "^0.6.0", + "lint-staged": "^15.4.3", + "prettier": "^3.5.2", + "prettier-plugin-tailwindcss": "^0.6.11", + "style-loader": "^4.0.0", + "ts-jest": "^29.2.6", "ts-loader": "^9.5.1", + "typescript": "^5.7.2", "webpack": "^5.95.0", "webpack-cli": "^5.1.4", - "@vscode/test-cli": "^0.0.10", - "@vscode/test-electron": "^2.4.1" + "webpack-node-externals": "^3.0.0" + }, + "dependencies": { + "@types/dotenv": "^6.1.1", + "bufferutil": "^4.0.9", + "dotenv": "^16.4.7", + "dotenv-webpack": "^8.1.0", + "utf-8-validate": "^6.0.5", + "ws": "^8.18.0" + }, + "icon": "./assets/eco_logo.png", + "contributes": { + "viewsContainers": { + "activitybar": [ + { + "id": "ecooptimizer", + "title": "Eco", + "icon": "assets/eco-icon.png" + } + ] + }, + "views": { + "ecooptimizer": [ + { + "id": "ecooptimizer.refactorView", + "name": "Refactoring Details", + "icon": "assets/eco-icon.png" + }, + { + "id": "ecooptimizer.smellsView", + "name": "Code Smells", + "icon": "assets/eco-icon.png" + }, + { + "id": "ecooptimizer.metricsView", + "name": "Carbon Metrics", + "icon": "assets/eco-icon.png" + }, + { + "id": "ecooptimizer.filterView", + "name": "Filter Smells", + "icon": "assets/eco-icon.png" + } + ] + }, + "viewsWelcome": [ + { + "view": "ecooptimizer.refactorView", + "contents": "Refactoring is currently not in progress. Try selecting a smell in the Code Smells view to start refactoring.", + "when": "!refactoringInProgress" + }, + { + "view": "ecooptimizer.smellsView", + "contents": "No code smells detected yet. Configure your workspace to start analysis.\n\n[Configure Workspace](command:ecooptimizer.configureWorkspace)\n\n[Read the docs](https://code.visualstudio.com/api) to learn how to use Eco-Optimizer.", + "when": "!workspaceState.workspaceConfigured" + }, + { + "view": "ecooptimizer.metricsView", + "contents": "No energy savings to declare. Configure your workspace to start saving energy!\n\n[Configure Workspace](command:ecooptimizer.configureWorkspace)\n\n[Read the docs](https://code.visualstudio.com/api) to learn how to use Eco-Optimizer.", + "when": "!workspaceState.workspaceConfigured" + } + ], + "commands": [ + { + "command": "ecooptimizer.startServer", + "title": "Start EcoOptimizer Server", + "category": "Eco" + }, + { + "command": "ecooptimizer.stopServer", + "title": "Stop EcoOptimizer Server", + "category": "Eco" + }, + { + "command": "ecooptimizer.configureWorkspace", + "title": "Configure Workspace", + "category": "Eco" + }, + { + "command": "ecooptimizer.resetConfiguration", + "title": "Reset Configuration", + "category": "Eco" + }, + { + "command": "ecooptimizer.wipeWorkCache", + "title": "Clear Smells Cache", + "category": "Eco" + }, + { + "command": "ecooptimizer.toggleSmellFilter", + "title": "Toggle Smell", + "category": "Eco" + }, + { + "command": "ecooptimizer.editSmellFilterOption", + "title": "Edit Option", + "icon": "$(edit)", + "category": "Eco" + }, + { + "command": "ecooptimizer.selectAllFilterSmells", + "title": "Select All Smells", + "category": "Eco" + }, + { + "command": "ecooptimizer.deselectAllFilterSmells", + "title": "Deselect All Smells", + "category": "Eco" + }, + { + "command": "ecooptimizer.setFilterDefaults", + "title": "Set Filter Defaults", + "category": "Eco", + "when": "view == ecooptimizer.filterView && !refactoringInProgress" + }, + { + "command": "ecooptimizer.detectSmellsFolder", + "title": "Detect Smells for All Files", + "icon": "$(search)", + "category": "Eco" + }, + { + "command": "ecooptimizer.detectSmellsFile", + "title": "Detect Smells", + "icon": "$(search)", + "category": "Eco" + }, + { + "command": "ecooptimizer.refactorAllSmellsOfType", + "title": "Refactor Smells By Type", + "icon": "$(tools)", + "category": "Eco" + }, + { + "command": "ecooptimizer.refactorSmell", + "title": "Refactor Smell", + "icon": "$(tools)", + "category": "Eco" + }, + { + "command": "ecooptimizer.acceptRefactoring", + "title": "Accept Refactoring", + "category": "Eco", + "icon": "$(check)" + }, + { + "command": "ecooptimizer.rejectRefactoring", + "title": "Reject Refactoring", + "category": "Eco", + "icon": "$(close)" + }, + { + "command": "ecooptimizer.exportMetricsData", + "title": "Export Metrics Data as JSON", + "category": "Eco" + }, + { + "command": "ecooptimizer.clearMetricsData", + "title": "Clear Metrics Data", + "category": "Eco" + }, + { + "command": "ecooptimizer.metricsView.refresh", + "title": "Refresh Metrics Data", + "icon": "$(sync)", + "category": "Eco" + }, + { + "command": "ecooptimizer.toggleSmellLintingOn", + "title": "Toggle Smell Linting", + "category": "Eco", + "icon": { + "light": "assets/darkgreen_leaf.png", + "dark": "assets/green_leaf.png" + } + }, + { + "command": "ecooptimizer.toggleSmellLintingOff", + "title": "Toggle Smell Linting", + "category": "Eco", + "icon": { + "light": "assets/white_leaf.png", + "dark": "assets/black_leaf.png" + } + } + ], + "menus": { + "view/title": [ + { + "command": "ecooptimizer.resetConfiguration", + "when": "view == ecooptimizer.smellsView && workspaceState.workspaceConfigured && !refactoringInProgress", + "group": "resource" + }, + { + "command": "ecooptimizer.wipeWorkCache", + "when": "view == ecooptimizer.smellsView && workspaceState.workspaceConfigured && !refactoringInProgress", + "group": "resource" + }, + { + "command": "ecooptimizer.selectAllFilterSmells", + "when": "view == ecooptimizer.filterView && !refactoringInProgress", + "group": "resource" + }, + { + "command": "ecooptimizer.deselectAllFilterSmells", + "when": "view == ecooptimizer.filterView && !refactoringInProgress", + "group": "resource" + }, + { + "command": "ecooptimizer.setFilterDefaults", + "when": "view == ecooptimizer.filterView && !refactoringInProgress", + "group": "resource" + }, + { + "command": "ecooptimizer.exportMetricsData", + "when": "view == ecooptimizer.metricsView", + "group": "resource" + }, + { + "command": "ecooptimizer.clearMetricsData", + "when": "view == ecooptimizer.metricsView", + "group": "resource" + }, + { + "command": "ecooptimizer.metricsView.refresh", + "when": "view == ecooptimizer.metricsView", + "group": "navigation" + } + ], + "view/item/context": [ + { + "command": "ecooptimizer.editSmellFilterOption", + "when": "viewItem == smellOption && !refactoringInProgress", + "group": "inline" + }, + { + "command": "ecooptimizer.detectSmellsFolder", + "when": "view == ecooptimizer.smellsView && viewItem == directory && !refactoringInProgress", + "group": "inline" + }, + { + "command": "ecooptimizer.detectSmellsFile", + "when": "view == ecooptimizer.smellsView && (viewItem == file || viewItem == file_with_smells) && !refactoringInProgress", + "group": "inline" + }, + { + "command": "ecooptimizer.refactorAllSmellsOfType", + "when": "view == ecooptimizer.smellsView && viewItem == file_with_smells && !refactoringInProgress", + "group": "inline" + }, + { + "command": "ecooptimizer.refactorSmell", + "when": "view == ecooptimizer.smellsView && viewItem == smell && !refactoringInProgress", + "group": "inline" + } + ], + "editor/title": [ + { + "command": "ecooptimizer.toggleSmellLintingOn", + "when": "workspaceState.workspaceConfigured && editorLangId == python && ecooptimizer.smellLintingEnabled", + "group": "navigation" + }, + { + "command": "ecooptimizer.toggleSmellLintingOff", + "when": "workspaceState.workspaceConfigured && editorLangId == python && !ecooptimizer.smellLintingEnabled", + "group": "navigation" + } + ] + }, + "configuration": { + "title": "EcoOptimizer", + "properties": { + "ecooptimizer.detection.smellsColours": { + "order": 1, + "type": "object", + "additionalProperties": false, + "description": "Configure highlight colours for smells.", + "default": { + "long-element-chain": "lightblue", + "too-many-arguments": "lightcoral", + "long-lambda-expression": "mediumpurple", + "long-message-chain": "lightpink", + "cached-repeated-calls": "lightgreen", + "string-concat-loop": "lightsalmon", + "no-self-use": "lightcyan", + "use-a-generator": "yellow" + }, + "properties": { + "long-element-chain": { + "type": "string", + "default": "lightblue", + "description": "Colour (css syntax) for highlighting long element chains." + }, + "too-many-arguments": { + "type": "string", + "default": "lightcoral", + "description": "Colour (css syntax) for highlighting functions with too many arguments." + }, + "long-lambda-expression": { + "type": "string", + "default": "mediumpurple", + "description": "Colour (css syntax) for highlighting long lambda expressions." + }, + "long-message-chain": { + "type": "string", + "default": "lightpink", + "description": "Colour (css syntax) for highlighting long message chains." + }, + "cached-repeated-calls": { + "type": "string", + "default": "lightgreen", + "description": "Colour (css syntax) for highlighting cached repeated calls." + }, + "string-concat-loop": { + "type": "string", + "default": "lightsalmon", + "description": "Colour (css syntax) for highlighting string concatenation in loops." + }, + "no-self-use": { + "type": "string", + "default": "lightcyan", + "description": "Colour (css syntax) for highlighting methods with no self-use." + }, + "use-a-generator": { + "type": "string", + "default": "yellow", + "description": "Colour (css syntax) for highlighting places where a generator could be used." + } + } + }, + "ecooptimizer.detection.useSingleColour": { + "order": 2, + "type": "boolean", + "default": false, + "description": "Use a single colour for all smells. If enabled, the colour defined below will be used." + }, + "ecooptimizer.detection.singleHighlightColour": { + "order": 3, + "type": "string", + "default": "yellow", + "markdownDescription": "Colour (css syntax) to use for all smells if **Use Single Colour** is enabled." + }, + "ecooptimizer.detection.highlightStyle": { + "order": 0, + "type": "string", + "enum": [ + "underline", + "flashlight", + "border-arrow" + ], + "markdownEnumDescriptions": [ + "Your average wavy line", + "No pixel left untouched", + "Basically how it sounds" + ], + "default": "underline", + "description": "Choose a highlight style for all smells." + } + } + } } } diff --git a/src/api/backend.ts b/src/api/backend.ts index cdc931c..00a8e23 100644 --- a/src/api/backend.ts +++ b/src/api/backend.ts @@ -1,55 +1,271 @@ -const BASE_URL = "http://127.0.0.1:8000"; // API URL for Python backend +import {basename} from 'path'; +import { envConfig } from '../utils/envConfig'; +import { serverStatus } from '../emitters/serverStatus'; +import { ServerStatusType } from '../emitters/serverStatus'; +import { ecoOutput } from '../extension'; +// Base URL for backend API endpoints constructed from environment configuration +const BASE_URL = `http://${envConfig.SERVER_URL}`; -// Fetch detected smells for a given file -export async function fetchSmells(filePath: string): Promise { - const url = `${BASE_URL}/smells?file_path=${encodeURIComponent(filePath)}`; +/** + * Verifies backend service availability and updates extension status. + * Performs health check by hitting the /health endpoint and handles three scenarios: + * 1. Successful response (200-299) - marks server as UP + * 2. Error response - marks server as DOWN with status code + * 3. Network failure - marks server as DOWN with error details + */ +export async function checkServerStatus(): Promise { try { - const response = await fetch(url); - if (!response.ok) { - throw new Error(`Error fetching smells: ${response.statusText}`); + ecoOutput.info('[backend.ts] Checking backend server health status...'); + const response = await fetch(`${BASE_URL}/health`); + + if (response.ok) { + serverStatus.setStatus(ServerStatusType.UP); + ecoOutput.trace('[backend.ts] Backend server is healthy'); + } else { + serverStatus.setStatus(ServerStatusType.DOWN); + ecoOutput.warn(`[backend.ts] Backend server unhealthy status: ${response.status}`); } - const smellsList = await response.json(); - return smellsList as Smell[]; } catch (error) { - console.error("Error in getSmells:", error); - throw error; + serverStatus.setStatus(ServerStatusType.DOWN); + ecoOutput.error( + `[backend.ts] Server connection failed: ${error instanceof Error ? error.message : 'Unknown error'}` + ); + } +} + +/** + * Initializes and synchronizes logs with the backend server. + * + * This function sends a POST request to the backend to initialize logging + * for the specified log directory. If the request is successful, logging + * is initialized; otherwise, an error is logged, and an error message is + * displayed to the user. + * + * @param log_dir - The directory path where logs are stored. + * @returns A promise that resolves to `true` if logging is successfully initialized, + * or `false` if an error occurs. + */ +export async function initLogs(log_dir: string): Promise { + const url = `${BASE_URL}/logs/init`; + + try { + console.log('Initializing and synching logs with backend'); + + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ log_dir }), + }); + + if (!response.ok) { + console.error(`Unable to initialize logging: ${JSON.stringify(response)}`); + ecoOutput.error( + `Unable to initialize logging: ${JSON.stringify(response)}`, + ); + + return false; + } + + return true; + } catch (error: any) { + console.error(`Eco: Unable to initialize logging: ${error.message}`); + ecoOutput.error( + 'Eco: Unable to reach the backend. Please check your connection.', + ); + return false; } } -// Request refactoring for a specific smell -export async function refactorSmell(filePath: string, smell: Smell): Promise<{ - refactoredCode: string; - energyDifference: number; - updatedSmells: Smell[]; -}> { +/** + * Analyzes source code for code smells using backend detection service. + * @param filePath - Absolute path to the source file for analysis + * @param enabledSmells - Configuration object specifying which smells to detect + * @returns Promise resolving to smell detection results and HTTP status + * @throws Error when: + * - Network request fails + * - Backend returns non-OK status + * - Response contains invalid data format + */ +export async function fetchSmells( + filePath: string, + enabledSmells: Record>, +): Promise<{ smells: Smell[]; status: number }> { + const url = `${BASE_URL}/smells`; + const fileName = basename(filePath); + ecoOutput.info(`[backend.ts] Starting smell detection for: ${fileName}`); + + try { + ecoOutput.debug(`[backend.ts] Request payload for ${fileName}:`, { + file_path: filePath, + enabled_smells: enabledSmells + }); + + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + file_path: filePath, + enabled_smells: enabledSmells, + }), + }); + + if (!response.ok) { + const errorMsg = `Backend request failed (${response.status})`; + ecoOutput.error(`[backend.ts] ${errorMsg}`); + try { + const errorBody = await response.json(); + ecoOutput.error(`[backend.ts] Backend error details:`, errorBody); + } catch (e: any) { + ecoOutput.error(`[backend.ts] Could not parse error response`); + } + throw new Error(errorMsg); + } + + const smellsList = await response.json(); + + // Detailed logging of the response + ecoOutput.info(`[backend.ts] Detection complete for ${fileName}`); + ecoOutput.debug(`[backend.ts] Raw response headers for ${fileName}:`, Object.fromEntries(response.headers.entries())); + ecoOutput.debug(`[backend.ts] Full response for ${fileName}:`, { + status: response.status, + statusText: response.statusText, + body: smellsList + }); + + // Detailed smell listing + ecoOutput.info(`[backend.ts] Detected ${smellsList.length} smells in ${fileName}`); + if (smellsList.length > 0) { + ecoOutput.debug(`[backend.ts] Complete smells list for ${fileName}:`, smellsList); + ecoOutput.debug(`[backend.ts] Verbose smell details for ${fileName}:`, + smellsList.map((smell: Smell) => ({ + type: smell.symbol, + location: `${smell.path}:${smell.occurences}`, + message: smell.message, + context: smell.messageId + })) + ); + } + + return { smells: smellsList, status: response.status }; + + } catch (error: any) { + ecoOutput.error(`[backend.ts] Smell detection failed for ${fileName}: ${error.message}`); + if (error instanceof Error && error.stack) { + ecoOutput.trace(`[backend.ts] Error stack info:`, error.stack); + } + throw new Error(`Detection failed: ${error.message}`); + } +} + +/** + * Executes code refactoring for a specific detected smell pattern. + * @param smell - The smell object containing detection details + * @param workspacePath - The path to the workspace. + * @returns Promise resolving to refactoring result data + * @throws Error when: + * - Workspace path is not provided + * - Refactoring request fails + * - Network errors occur + */ +export async function backendRefactorSmell( + smell: Smell, + workspacePath: string, +): Promise { const url = `${BASE_URL}/refactor`; + + // Validate workspace configuration + if (!workspacePath) { + ecoOutput.error('[backend.ts] Refactoring aborted: No workspace path'); + throw new Error('No workspace path provided'); + } + + ecoOutput.info(`[backend.ts] Starting refactoring for smell: ${smell.symbol}`); + console.log('Starting refactoring for smell:', smell); + + try { + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + sourceDir: workspacePath, + smell, + }), + }); + + if (!response.ok) { + const errorData = await response.json(); + ecoOutput.error(`[backend.ts] Refactoring failed: ${errorData.detail || 'Unknown error'}`); + throw new Error(errorData.detail || 'Refactoring failed'); + } + + const result = await response.json(); + ecoOutput.info(`[backend.ts] Refactoring successful for ${smell.symbol}`); + return result; + + } catch (error: any) { + ecoOutput.error(`[backend.ts] Refactoring error: ${error.message}`); + throw new Error(`Refactoring failed: ${error.message}`); + } +} + +/** + * Sends a request to the backend to refactor all smells of a type. + * + * @param smell - The smell to refactor. + * @param workspacePath - The path to the workspace. + * @returns A promise resolving to the refactored data or throwing an error if unsuccessful. + */ +export async function backendRefactorSmellType( + smell: Smell, + workspacePath: string +): Promise { + const url = `${BASE_URL}/refactor-by-type`; + const filePath = smell.path; + const smellType = smell.symbol; + + // Validate workspace configuration + if (!workspacePath) { + ecoOutput.error('[backend.ts] Refactoring aborted: No workspace path'); + throw new Error('No workspace path provided'); + } + + ecoOutput.info(`[backend.ts] Starting refactoring for smells of type "${smellType}" in "${filePath}"`); + + // Prepare the payload for the backend const payload = { - file_path: filePath, - smell: smell, + sourceDir: workspacePath, + smellType, + firstSmell: smell, }; try { const response = await fetch(url, { - method: "POST", + method: 'POST', headers: { - "Content-Type": "application/json", + 'Content-Type': 'application/json', }, body: JSON.stringify(payload), }); if (!response.ok) { - throw new Error(`Error refactoring smell: ${response.statusText}`); + const errorData = await response.json(); + ecoOutput.error(`[backend.ts] Refactoring failed: ${errorData.detail || 'Unknown error'}`); + throw new Error(errorData.detail || 'Refactoring failed'); } - const refactorResult = await response.json(); - return refactorResult as { - refactoredCode: string; - energyDifference: number; - updatedSmells: Smell[]; - }; - } catch (error) { - console.error("Error in refactorSmell:", error); - throw error; + const result = await response.json(); + ecoOutput.info(`[backend.ts] Refactoring successful for ${smell.symbol}`); + return result; + + } catch (error: any) { + ecoOutput.error(`[backend.ts] Refactoring error: ${error.message}`); + throw new Error(`Refactoring failed: ${error.message}`); } -} +} \ No newline at end of file diff --git a/src/commands/configureWorkspace.ts b/src/commands/configureWorkspace.ts new file mode 100644 index 0000000..5e1901e --- /dev/null +++ b/src/commands/configureWorkspace.ts @@ -0,0 +1,146 @@ +import * as vscode from 'vscode'; +import * as path from 'path'; +import * as fs from 'fs'; +import { envConfig } from '../utils/envConfig'; + +/** + * Initializes workspace configuration by prompting user to select a Python project folder. + * This is the main entry point for workspace configuration and delegates to folder-specific logic. + * + * @param context - VS Code extension context containing workspace state management + */ +export async function configureWorkspace( + context: vscode.ExtensionContext, +): Promise { + await configurePythonFolder(context); +} + +/** + * Recursively identifies Python project folders by scanning for: + * - Python files (*.py) + * - \_\_init\_\_.py package markers + * Maintains a hierarchical understanding of Python projects in the workspace. + * + * @param folderPath - Absolute filesystem path to scan + * @returns Array of qualified Python project paths + */ +function findPythonFoldersRecursively(folderPath: string): string[] { + let pythonFolders: string[] = []; + let hasPythonFiles = false; + + try { + const files = fs.readdirSync(folderPath); + + // Validate current folder contains Python artifacts + if ( + files.includes('__init__.py') || + files.some((file) => file.endsWith('.py')) + ) { + hasPythonFiles = true; + } + + // Recursively process subdirectories + for (const file of files) { + const filePath = path.join(folderPath, file); + if (fs.statSync(filePath).isDirectory()) { + const subfolderPythonFolders = findPythonFoldersRecursively(filePath); + if (subfolderPythonFolders.length > 0) { + hasPythonFiles = true; + pythonFolders.push(...subfolderPythonFolders); + } + } + } + + // Include current folder if Python content found at any level + if (hasPythonFiles) { + pythonFolders.push(folderPath); + } + } catch (error) { + vscode.window.showErrorMessage( + `Workspace scanning error in ${path.basename(folderPath)}: ${(error as Error).message}`, + ); + } + + return pythonFolders; +} + +/** + * Guides user through Python workspace selection process with validation. + * Presents filtered list of valid Python project folders and handles selection. + * + * @param context - Extension context for state persistence + */ +async function configurePythonFolder( + context: vscode.ExtensionContext, +): Promise { + const workspaceFolders = vscode.workspace.workspaceFolders; + + if (!workspaceFolders?.length) { + vscode.window.showErrorMessage( + 'No workspace detected. Please open a project folder first.', + ); + return; + } + + // Identify all Python project roots + const validPythonFolders = workspaceFolders + .map((folder) => folder.uri.fsPath) + .flatMap(findPythonFoldersRecursively); + + if (validPythonFolders.length === 0) { + vscode.window.showErrorMessage( + 'No Python projects found. Workspace must contain .py files or __init__.py markers.', + ); + return; + } + + // Present interactive folder selection + const selectedFolder = await vscode.window.showQuickPick( + validPythonFolders.map((folder) => ({ + label: path.basename(folder), + description: folder, + detail: `Python content: ${fs + .readdirSync(folder) + .filter((file) => file.endsWith('.py') || file === '__init__.py') + .join(', ')}`, + folderPath: folder, + })), + { + placeHolder: 'Select Python project root', + matchOnDescription: true, + matchOnDetail: true, + }, + ); + + if (selectedFolder) { + await updateWorkspace(context, selectedFolder.folderPath); + vscode.window.showInformationMessage( + `Configured workspace: ${path.basename(selectedFolder.folderPath)}`, + ); + } +} + +/** + * Persists workspace configuration and updates extension context. + * Triggers view refreshes to reflect new workspace state. + * + * @param context - Extension context for state management + * @param workspacePath - Absolute path to selected workspace root + */ +export async function updateWorkspace( + context: vscode.ExtensionContext, + workspacePath: string, +): Promise { + // Persist workspace path + await context.workspaceState.update( + envConfig.WORKSPACE_CONFIGURED_PATH!, + workspacePath, + ); + + // Update extension context for UI state management + vscode.commands.executeCommand( + 'setContext', + 'workspaceState.workspaceConfigured', + true, + ); +} diff --git a/src/commands/detectSmells.ts b/src/commands/detectSmells.ts deleted file mode 100644 index b2e5929..0000000 --- a/src/commands/detectSmells.ts +++ /dev/null @@ -1,53 +0,0 @@ -import * as vscode from "vscode"; -import { FileHighlighter } from "../ui/fileHighlighter"; -import { getEditorAndFilePath } from "../utils/editorUtils"; -import * as fs from "fs"; -import { Smell } from "../types"; -import { fetchSmells } from "../api/backend"; - -export async function getSmells(filePath: string, context: vscode.ExtensionContext) { - try { - const smellsList : Smell[] = await fetchSmells(filePath); - if (smellsList.length === 0) { - throw new Error("Detected smells data is invalid or empty."); - } - - return smellsList; - } catch(error) { - console.error("Error detecting smells:", error); - vscode.window.showErrorMessage(`Eco: Error detecting smells: ${error}`); - return; - } -} - -export async function detectSmells(context: vscode.ExtensionContext){ - const {editor, filePath} = getEditorAndFilePath(); - - if (!editor) { - vscode.window.showErrorMessage("Eco: Unable to proceed as no active editor found."); - console.error("No active editor found to detect smells. Returning back."); - return; - } - if (!filePath) { - vscode.window.showErrorMessage("Eco: Unable to proceed as active editor does not have a valid file path."); - console.error("No valid file path found to detect smells. Returning back."); - return; - } - - vscode.window.showInformationMessage("Eco: Detecting smells..."); - console.log("Detecting smells in detectSmells"); - - const smellsData = await getSmells(filePath, context); - - if (!smellsData){ - console.log("No valid smells data found. Returning."); - vscode.window.showErrorMessage("Eco: No smells are present in current file."); - return; - } - - console.log("Detected smells data: ", smellsData); - vscode.window.showInformationMessage(`Eco: Detected ${smellsData.length} smells in the file.`); - - FileHighlighter.highlightSmells(editor, smellsData); - vscode.window.showInformationMessage("Eco: Detected code smells have been highlighted."); -} diff --git a/src/commands/detection/detectSmells.ts b/src/commands/detection/detectSmells.ts new file mode 100644 index 0000000..6dbb5a8 --- /dev/null +++ b/src/commands/detection/detectSmells.ts @@ -0,0 +1,193 @@ +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import * as path from 'path'; + +import { fetchSmells } from '../../api/backend'; +import { SmellsViewProvider } from '../../providers/SmellsViewProvider'; +import { getEnabledSmells } from '../../utils/smellsData'; +import { serverStatus, ServerStatusType } from '../../emitters/serverStatus'; +import { SmellsCacheManager } from '../../context/SmellsCacheManager'; +import { ecoOutput } from '../../extension'; + +/** + * Performs code smell analysis on a single Python file with comprehensive state management. + * Only shows user notifications for critical events requiring attention. + * + * @param filePath - Absolute path to the Python file to analyze + * @param smellsViewProvider - Provider for updating the UI with results + * @param smellsCacheManager - Manager for cached smell results + */ +export async function detectSmellsFile( + filePath: string, + smellsViewProvider: SmellsViewProvider, + smellsCacheManager: SmellsCacheManager, +): Promise { + const shouldProceed = await precheckAndMarkQueued( + filePath, + smellsViewProvider, + smellsCacheManager, + ); + + if (!shouldProceed) return; + + // Transform enabled smells into backend-compatible format + const enabledSmells = getEnabledSmells(); + const enabledSmellsForBackend = Object.fromEntries( + Object.entries(enabledSmells).map(([key, value]) => [key, value.options]), + ); + + try { + ecoOutput.info(`[detection.ts] Analyzing: ${path.basename(filePath)}`); + const { smells, status } = await fetchSmells(filePath, enabledSmellsForBackend); + + // Handle backend response + if (status === 200) { + if (smells.length > 0) { + ecoOutput.info(`[detection.ts] Detected ${smells.length} smells`); + smellsViewProvider.setStatus(filePath, 'passed'); + await smellsCacheManager.setCachedSmells(filePath, smells); + smellsViewProvider.setSmells(filePath, smells); + } else { + ecoOutput.info('[detection.ts] File has no detectable smells'); + smellsViewProvider.setStatus(filePath, 'no_issues'); + await smellsCacheManager.setCachedSmells(filePath, []); + } + } else { + const msg = `Analysis failed for ${path.basename(filePath)} (status ${status})`; + ecoOutput.error(`[detection.ts] ${msg}`); + smellsViewProvider.setStatus(filePath, 'failed'); + vscode.window.showErrorMessage(msg); + } + } catch (error: any) { + const msg = `Analysis failed: ${error.message}`; + ecoOutput.error(`[detection.ts] ${msg}`); + smellsViewProvider.setStatus(filePath, 'failed'); + vscode.window.showErrorMessage(msg); + } +} + +/** + * Validates conditions before analysis. Only shows notifications when: + * - Using cached results (info) + * - Server is down (warning) + * - No smells configured (warning) + * + * @returns boolean indicating whether analysis should proceed + */ +async function precheckAndMarkQueued( + filePath: string, + smellsViewProvider: SmellsViewProvider, + smellsCacheManager: SmellsCacheManager, +): Promise { + // Validate file scheme and extension + const fileUri = vscode.Uri.file(filePath); + if (fileUri.scheme !== 'file') { + return false; + } + + if (!filePath.endsWith('.py')) { + return false; + } + + // Check for cached results + if (smellsCacheManager.hasCachedSmells(filePath)) { + const cached = smellsCacheManager.getCachedSmells(filePath); + ecoOutput.info( + `[detection.ts] Using cached results for ${path.basename(filePath)}`, + ); + + if (cached && cached.length > 0) { + smellsViewProvider.setStatus(filePath, 'passed'); + smellsViewProvider.setSmells(filePath, cached); + } else { + smellsViewProvider.setStatus(filePath, 'no_issues'); + } + return false; + } + + // Check server availability + if (serverStatus.getStatus() === ServerStatusType.DOWN) { + const msg = 'Backend server unavailable - using cached results where available'; + ecoOutput.warn(`[detection.ts] ${msg}`); + vscode.window.showWarningMessage(msg); + smellsViewProvider.setStatus(filePath, 'server_down'); + return false; + } + + // Verify at least one smell detector is enabled + const enabledSmells = getEnabledSmells(); + if (Object.keys(enabledSmells).length === 0) { + const msg = 'No smell detectors enabled in settings'; + ecoOutput.warn(`[detection.ts] ${msg}`); + vscode.window.showWarningMessage(msg); + return false; + } + + smellsViewProvider.setStatus(filePath, 'queued'); + return true; +} + +/** + * Recursively analyzes Python files in a directory with progress indication. + * Shows a progress notification for the folder scan operation. + * + * @param folderPath - Absolute path to the folder to analyze + * @param smellsViewProvider - Provider for updating the UI with results + * @param smellsCacheManager - Manager for cached smell results + */ +export async function detectSmellsFolder( + folderPath: string, + smellsViewProvider: SmellsViewProvider, + smellsCacheManager: SmellsCacheManager, +): Promise { + return vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: `Scanning for Python files in ${path.basename(folderPath)}...`, + cancellable: false, + }, + async () => { + const pythonFiles: string[] = []; + + // Recursive directory walker for Python files + function walk(dir: string): void { + try { + const entries = fs.readdirSync(dir); + for (const entry of entries) { + const fullPath = path.join(dir, entry); + const stat = fs.statSync(fullPath); + + if (stat.isDirectory()) { + walk(fullPath); + } else if (stat.isFile() && fullPath.endsWith('.py')) { + pythonFiles.push(fullPath); + } + } + } catch (error) { + ecoOutput.error( + `[detection.ts] Scan error: ${error instanceof Error ? error.message : 'Unknown error'}`, + ); + } + } + + walk(folderPath); + ecoOutput.info(`[detection.ts] Found ${pythonFiles.length} files to analyze`); + + if (pythonFiles.length === 0) { + vscode.window.showWarningMessage( + `No Python files found in ${path.basename(folderPath)}`, + ); + return; + } + + vscode.window.showInformationMessage( + `Analyzing ${pythonFiles.length} Python files...`, + ); + + // Process each found Python file + for (const file of pythonFiles) { + await detectSmellsFile(file, smellsViewProvider, smellsCacheManager); + } + }, + ); +} diff --git a/src/commands/detection/wipeWorkCache.ts b/src/commands/detection/wipeWorkCache.ts new file mode 100644 index 0000000..80533fc --- /dev/null +++ b/src/commands/detection/wipeWorkCache.ts @@ -0,0 +1,30 @@ +import * as vscode from 'vscode'; + +import { SmellsCacheManager } from '../../context/SmellsCacheManager'; +import { SmellsViewProvider } from '../../providers/SmellsViewProvider'; + +/** + * Clears the smells cache and refreshes the UI. + * @param smellsCacheManager - Manages the caching of smells and file hashes. + * @param smellsViewProvider - The UI provider for updating the tree view. + */ +export async function wipeWorkCache( + smellsCacheManager: SmellsCacheManager, + smellsViewProvider: SmellsViewProvider, +) { + const userResponse = await vscode.window.showWarningMessage( + 'Are you sure you want to clear the entire workspace analysis? This action cannot be undone.', + { modal: true }, + 'Confirm', + ); + + if (userResponse === 'Confirm') { + smellsCacheManager.clearAllCachedSmells(); + smellsViewProvider.clearAllStatuses(); + smellsViewProvider.refresh(); + + vscode.window.showInformationMessage('Workspace analysis cleared successfully.'); + } else { + vscode.window.showInformationMessage('Operation cancelled.'); + } +} diff --git a/src/commands/refactor/acceptRefactoring.ts b/src/commands/refactor/acceptRefactoring.ts new file mode 100644 index 0000000..eca8b47 --- /dev/null +++ b/src/commands/refactor/acceptRefactoring.ts @@ -0,0 +1,110 @@ +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import { SmellsViewProvider } from '../../providers/SmellsViewProvider'; +import { MetricsViewProvider } from '../../providers/MetricsViewProvider'; +import { RefactoringDetailsViewProvider } from '../../providers/RefactoringDetailsViewProvider'; +import { SmellsCacheManager } from '../../context/SmellsCacheManager'; +import { ecoOutput } from '../../extension'; +import { hideRefactorActionButtons } from '../../utils/refactorActionButtons'; +import { detectSmellsFile } from '../detection/detectSmells'; +import { closeAllTrackedDiffEditors } from '../../utils/trackedDiffEditors'; +import { envConfig } from '../../utils/envConfig'; + +/** + * Handles acceptance and application of refactoring changes to the codebase. + * Performs the following operations: + * 1. Applies refactored changes to target and affected files + * 2. Updates energy savings metrics + * 3. Clears cached smell data for modified files + * 4. Updates UI components to reflect changes + * + * @param refactoringDetailsViewProvider - Provides access to refactoring details + * @param metricsDataProvider - Handles metrics tracking and updates + * @param smellsCacheManager - Manages smell detection cache invalidation + * @param smellsViewProvider - Controls the smells view UI updates + * @param context - VS Code extension context + */ +export async function acceptRefactoring( + context: vscode.ExtensionContext, + refactoringDetailsViewProvider: RefactoringDetailsViewProvider, + metricsDataProvider: MetricsViewProvider, + smellsCacheManager: SmellsCacheManager, + smellsViewProvider: SmellsViewProvider, +): Promise { + const targetFile = refactoringDetailsViewProvider.targetFile; + const affectedFiles = refactoringDetailsViewProvider.affectedFiles; + + // Validate refactoring data exists + if (!targetFile || !affectedFiles) { + console.log('no data'); + ecoOutput.error('[refactorActions.ts] Error: No refactoring data available'); + vscode.window.showErrorMessage('No refactoring data available.'); + return; + } + + try { + ecoOutput.info( + `[refactorActions.ts] Applying refactoring to target file: ${targetFile.original}`, + ); + + // Apply refactored changes to filesystem + fs.copyFileSync(targetFile.refactored, targetFile.original); + affectedFiles.forEach((file) => { + fs.copyFileSync(file.refactored, file.original); + ecoOutput.info(`[refactorActions.ts] Updated affected file: ${file.original}`); + }); + + // Update metrics if energy savings data exists + if ( + refactoringDetailsViewProvider.energySaved && + refactoringDetailsViewProvider.targetSmell + ) { + metricsDataProvider.updateMetrics( + targetFile.original, + refactoringDetailsViewProvider.energySaved, + refactoringDetailsViewProvider.targetSmell.symbol, + ); + ecoOutput.info('[refactorActions.ts] Updated energy savings metrics'); + } + + // Invalidate cache for modified files + await Promise.all([ + smellsCacheManager.clearCachedSmellsForFile(targetFile.original), + ...affectedFiles.map((file) => + smellsCacheManager.clearCachedSmellsForFile(file.original), + ), + ]); + ecoOutput.trace('[refactorActions.ts] Cleared smell caches for modified files'); + + // Update UI state + smellsViewProvider.setStatus(targetFile.original, 'outdated'); + affectedFiles.forEach((file) => { + smellsViewProvider.setStatus(file.original, 'outdated'); + }); + + await detectSmellsFile( + targetFile.original, + smellsViewProvider, + smellsCacheManager, + ); + + // Reset UI components + refactoringDetailsViewProvider.resetRefactoringDetails(); + closeAllTrackedDiffEditors(); + hideRefactorActionButtons(); + smellsViewProvider.refresh(); + + context.workspaceState.update(envConfig.UNFINISHED_REFACTORING!, undefined); + + vscode.window.showInformationMessage('Refactoring successfully applied'); + ecoOutput.info( + '[refactorActions.ts] Refactoring changes completed successfully', + ); + } catch (error) { + const errorDetails = error instanceof Error ? error.message : 'Unknown error'; + ecoOutput.error( + `[refactorActions.ts] Error applying refactoring: ${errorDetails}`, + ); + vscode.window.showErrorMessage('Failed to apply refactoring. Please try again.'); + } +} diff --git a/src/commands/refactor/refactor.ts b/src/commands/refactor/refactor.ts new file mode 100644 index 0000000..a57c5a2 --- /dev/null +++ b/src/commands/refactor/refactor.ts @@ -0,0 +1,132 @@ +import * as vscode from 'vscode'; +import * as path from 'path'; + +import { backendRefactorSmell, backendRefactorSmellType } from '../../api/backend'; +import { SmellsViewProvider } from '../../providers/SmellsViewProvider'; +import { RefactoringDetailsViewProvider } from '../../providers/RefactoringDetailsViewProvider'; +import { ecoOutput } from '../../extension'; +import { serverStatus, ServerStatusType } from '../../emitters/serverStatus'; +import { + showRefactorActionButtons, + hideRefactorActionButtons, +} from '../../utils/refactorActionButtons'; +import { registerDiffEditor } from '../../utils/trackedDiffEditors'; +import { envConfig } from '../../utils/envConfig'; + +/** + * Orchestrates the complete refactoring workflow. + * If isRefactorAllOfType is true, it sends a request to refactor all smells of the same type. + * + * - Pre-flight validation checks + * - Backend communication + * - UI updates and diff visualization + * - Success/error handling + * + * Shows carefully selected user notifications for key milestones and errors. + */ +export async function refactor( + smellsViewProvider: SmellsViewProvider, + refactoringDetailsViewProvider: RefactoringDetailsViewProvider, + smell: Smell, + context: vscode.ExtensionContext, + isRefactorAllOfType: boolean = false, +): Promise { + // Log and notify refactoring initiation + const action = isRefactorAllOfType + ? 'Refactoring all smells of type' + : 'Refactoring'; + ecoOutput.info(`[refactor.ts] ${action} ${smell.symbol} in ${smell.path}`); + vscode.window.showInformationMessage(`${action} ${smell.symbol}...`); + + // Validate workspace configuration + const workspacePath = context.workspaceState.get( + envConfig.WORKSPACE_CONFIGURED_PATH!, + ); + + if (!workspacePath) { + ecoOutput.error('[refactor.ts] Refactoring aborted: No workspace configured'); + vscode.window.showErrorMessage('Please configure workspace first'); + return; + } + + // Verify backend availability + if (serverStatus.getStatus() === ServerStatusType.DOWN) { + ecoOutput.warn('[refactor.ts] Refactoring blocked: Backend unavailable'); + vscode.window.showWarningMessage( + 'Cannot refactor - backend service unavailable', + ); + smellsViewProvider.setStatus(smell.path, 'server_down'); + return; + } + + // Update UI state + smellsViewProvider.setStatus(smell.path, 'queued'); + vscode.commands.executeCommand('setContext', 'refactoringInProgress', true); + + try { + // Execute backend refactoring + ecoOutput.trace(`[refactor.ts] Sending ${action} request...`); + const refactoredData = isRefactorAllOfType + ? await backendRefactorSmellType(smell, workspacePath) + : await backendRefactorSmell(smell, workspacePath); + + ecoOutput.info( + `[refactor.ts] Refactoring completed for ${path.basename(smell.path)}. ` + + `Energy saved: ${refactoredData.energySaved ?? 'N/A'} kg CO2`, + ); + + await context.workspaceState.update(envConfig.UNFINISHED_REFACTORING!, { + refactoredData, + smell, + }); + + startRefactorSession(smell, refactoredData, refactoringDetailsViewProvider); + } catch (error) { + ecoOutput.error( + `[refactor.ts] Refactoring failed: ${error instanceof Error ? error.message : 'Unknown error'}`, + ); + vscode.window.showErrorMessage('Refactoring failed. See output for details.'); + + refactoringDetailsViewProvider.resetRefactoringDetails(); + hideRefactorActionButtons(); + smellsViewProvider.setStatus(smell.path, 'failed'); + } finally { + vscode.commands.executeCommand('setContext', 'refactoringInProgress', false); + } +} + +export async function startRefactorSession( + smell: Smell, + refactoredData: RefactoredData, + refactoringDetailsViewProvider: RefactoringDetailsViewProvider, +): Promise { + // Update refactoring details view + refactoringDetailsViewProvider.updateRefactoringDetails( + smell, + refactoredData.targetFile, + refactoredData.affectedFiles, + refactoredData.energySaved, + ); + + // Show diff comparison + const targetFile = refactoredData.targetFile; + const fileName = path.basename(targetFile.original); + await vscode.commands.executeCommand( + 'vscode.diff', + vscode.Uri.file(targetFile.original), + vscode.Uri.file(targetFile.refactored), + `Refactoring Comparison (${fileName})`, + { preview: false }, + ); + registerDiffEditor( + vscode.Uri.file(targetFile.original), + vscode.Uri.file(targetFile.refactored), + ); + + await vscode.commands.executeCommand('ecooptimizer.refactorView.focus'); + showRefactorActionButtons(); + + vscode.window.showInformationMessage( + `Refactoring complete. Estimated savings: ${refactoredData.energySaved ?? 'N/A'} kg CO2`, + ); +} diff --git a/src/commands/refactor/rejectRefactoring.ts b/src/commands/refactor/rejectRefactoring.ts new file mode 100644 index 0000000..da1e282 --- /dev/null +++ b/src/commands/refactor/rejectRefactoring.ts @@ -0,0 +1,47 @@ +import * as vscode from 'vscode'; + +import { RefactoringDetailsViewProvider } from '../../providers/RefactoringDetailsViewProvider'; +import { hideRefactorActionButtons } from '../../utils/refactorActionButtons'; +import { closeAllTrackedDiffEditors } from '../../utils/trackedDiffEditors'; +import { SmellsViewProvider } from '../../providers/SmellsViewProvider'; +import { ecoOutput } from '../../extension'; +import { envConfig } from '../../utils/envConfig'; + +/** + * Handles rejection of proposed refactoring changes by: + * 1. Resetting UI components + * 2. Cleaning up diff editors + * 3. Restoring original file states + * 4. Providing user feedback + * + * Only shows a single notification to avoid interrupting workflow. + */ +export async function rejectRefactoring( + context: vscode.ExtensionContext, + refactoringDetailsViewProvider: RefactoringDetailsViewProvider, + smellsViewProvider: SmellsViewProvider, +): Promise { + ecoOutput.info('[refactorActions.ts] Refactoring changes discarded'); + vscode.window.showInformationMessage('Refactoring changes discarded'); + + try { + // Restore original file status if target exists + if (refactoringDetailsViewProvider.targetFile?.original) { + const originalPath = refactoringDetailsViewProvider.targetFile.original; + smellsViewProvider.setStatus(originalPath, 'passed'); + ecoOutput.trace(`[refactorActions.ts] Reset status for ${originalPath}`); + } + + // Clean up UI components + await closeAllTrackedDiffEditors(); + refactoringDetailsViewProvider.resetRefactoringDetails(); + hideRefactorActionButtons(); + + context.workspaceState.update(envConfig.UNFINISHED_REFACTORING!, undefined); + + ecoOutput.trace('[refactorActions.ts] Refactoring rejection completed'); + } catch (error) { + const errorMsg = `[refactorActions.ts] Error during rejection cleanup: ${error instanceof Error ? error.message : 'Unknown error'}`; + ecoOutput.error(errorMsg); + } +} diff --git a/src/commands/refactorSmell.ts b/src/commands/refactorSmell.ts deleted file mode 100644 index cee3211..0000000 --- a/src/commands/refactorSmell.ts +++ /dev/null @@ -1,77 +0,0 @@ -import * as vscode from "vscode"; -import { RefactorManager } from "../ui/refactorManager"; -import { getEditorAndFilePath } from "../utils/editorUtils"; -import { FileHighlighter } from "../ui/fileHighlighter"; -import { Smell } from "../types"; -import { fetchSmells, refactorSmell } from "../api/backend"; - - -async function refactorLine(smell: Smell, filePath: string, context: vscode.ExtensionContext){ - try { - const refactorResult = await refactorSmell(filePath, smell); - return refactorResult; - } catch (error) { - console.error("Error refactoring smell:", error); - vscode.window.showErrorMessage(`Eco: Error refactoring smell: ${error}`); - return; - } -} - -export async function refactorSelectedSmell(context: vscode.ExtensionContext) { - const {editor, filePath} = getEditorAndFilePath(); - - if (!editor) { - vscode.window.showErrorMessage("Eco: Unable to proceed as no active editor found."); - console.log("No active editor found to refactor smell. Returning back."); - return; - } - if (!filePath) { - vscode.window.showErrorMessage("Eco: Unable to proceed as active editor does not have a valid file path."); - console.log("No valid file path found to refactor smell. Returning back."); - return; - } - - // only account for one selection to be refactored for now - const selectedLine = editor.selection.start.line + 1; // update to VS code editor indexing - - const smellsData = await fetchSmells(filePath); - if (!smellsData || smellsData.length === 0) { - vscode.window.showErrorMessage("Eco: No smells detected in the file for refactoring."); - console.log("No smells found in the file for refactoring."); - return; - } - - const matchingSmells = smellsData.filter((smell: Smell) => { - return selectedLine === smell.line; - }); - - if (matchingSmells.length === 0) { - vscode.window.showInformationMessage("Eco: Selected line(s) does not include a refactorable code pattern. Please switch to a line with highlighted code smell."); - return; - } - - vscode.window.showInformationMessage('Eco: Refactoring smell on selected line.'); - console.log("Detecting smells in detectSmells on selected line"); - - //refactor the first found smell - //TODO UI that allows users to choose the smell to refactor - const refactorResult = await refactorLine(matchingSmells[0], filePath, context); - if (!refactorResult) { - vscode.window.showErrorMessage("Eco: Refactoring failed. See console for details."); - return; - } - const refactoredCode = refactorResult.refactoredCode; - const energyDifference = refactorResult.energyDifference; - const updatedSmells = refactorResult.updatedSmells; - - await RefactorManager.previewRefactor(editor, refactoredCode); - vscode.window.showInformationMessage( - `Eco: Refactoring completed. Energy difference: ${energyDifference.toFixed(4)}` - ); - - if (updatedSmells) { - FileHighlighter.highlightSmells(editor, updatedSmells); - } else { - vscode.window.showWarningMessage("Eco: No updated smells detected after refactoring."); - } -} diff --git a/src/commands/resetConfiguration.ts b/src/commands/resetConfiguration.ts new file mode 100644 index 0000000..bd3e004 --- /dev/null +++ b/src/commands/resetConfiguration.ts @@ -0,0 +1,35 @@ +import * as vscode from 'vscode'; +import { envConfig } from '../utils/envConfig'; + +/** + * Resets the workspace configuration by clearing the selected workspace path. + * Prompts the user for confirmation before performing the reset. + * + * @param context - The extension context for managing workspace state. + */ +export async function resetConfiguration( + context: vscode.ExtensionContext, +): Promise { + const confirm = await vscode.window.showWarningMessage( + 'Are you sure you want to reset the workspace configuration? This will remove the currently selected workspace and all analysis data will be lost.', + { modal: true }, + 'Reset', + ); + + if (confirm === 'Reset') { + await context.workspaceState.update( + envConfig.WORKSPACE_CONFIGURED_PATH!, + undefined, + ); + + vscode.commands.executeCommand( + 'setContext', + 'workspaceState.workspaceConfigured', + false, + ); + + return true; // signal that reset happened + } + + return false; +} diff --git a/src/commands/showLogs.ts b/src/commands/showLogs.ts new file mode 100644 index 0000000..c4f0441 --- /dev/null +++ b/src/commands/showLogs.ts @@ -0,0 +1,187 @@ +import * as vscode from 'vscode'; +import WebSocket from 'ws'; + +import { initLogs } from '../api/backend'; +import { envConfig } from '../utils/envConfig'; +import { serverStatus, ServerStatusType } from '../emitters/serverStatus'; + +const WEBSOCKET_BASE_URL = `ws://${envConfig.SERVER_URL}/logs`; + +class LogInitializationError extends Error { + constructor(message: string) { + super(message); + this.name = 'LogInitializationError'; + } +} + +export class LogManager { + private websockets: { [key: string]: WebSocket | undefined }; + private channels: { + [key: string]: { name: string; channel: vscode.LogOutputChannel | undefined }; + }; + private channelsCreated: boolean; + private context: vscode.ExtensionContext; + + constructor(context: vscode.ExtensionContext) { + this.context = context; + this.websockets = { + main: undefined, + detect: undefined, + refactor: undefined, + }; + this.channels = { + main: { name: 'EcoOptimizer: Main', channel: undefined }, + detect: { name: 'EcoOptimizer: Detect', channel: undefined }, + refactor: { name: 'EcoOptimizer: Refactor', channel: undefined }, + }; + this.channelsCreated = false; + + // Listen for server status changes + serverStatus.on('change', async (newStatus: ServerStatusType) => { + console.log('Server status changed:', newStatus); + if (newStatus === ServerStatusType.DOWN) { + this.channels.main.channel?.appendLine('Server connection lost'); + } else { + this.channels.main.channel?.appendLine('Server connection re-established.'); + await this.startLogging(); + } + }); + } + + /** + * Starts the logging process, including initializing logs and WebSockets. + * @param retries - Number of retry attempts. + * @param delay - Initial delay between retries (in milliseconds). + */ + public async startLogging(retries = 3, delay = 1000): Promise { + let logInitialized = false; + const logPath = this.context.logUri?.fsPath; + + if (!logPath) { + throw new LogInitializationError( + 'Missing extension context or logUri. Cannot initialize logging.', + ); + } + + for (let attempt = 1; attempt <= retries; attempt++) { + try { + if (!logInitialized) { + logInitialized = await initLogs(logPath); + + if (!logInitialized) { + throw new LogInitializationError( + `Failed to initialize logs at path: ${logPath}`, + ); + } + console.log('Log initialization successful.'); + } + + this.initializeWebSockets(); + console.log('Successfully initialized WebSockets. Logging is now active.'); + return; + } catch (error) { + const err = error as Error; + console.error(`[Attempt ${attempt}/${retries}] ${err.name}: ${err.message}`); + + if (attempt < retries) { + console.log(`Retrying in ${delay}ms...`); + await new Promise((resolve) => setTimeout(resolve, delay)); + delay *= 2; // Exponential backoff + } else { + throw new Error('Max retries reached. Logging process failed.'); + } + } + } + } + + /** + * Initializes WebSocket connections for logging. + */ + private initializeWebSockets(): void { + if (!this.channelsCreated) { + this.createOutputChannels(); + this.channelsCreated = true; + } + this.startWebSocket('main'); + this.startWebSocket('detect'); + this.startWebSocket('refactor'); + } + + /** + * Creates output channels for logging. + */ + private createOutputChannels(): void { + console.log('Creating output channels'); + for (const channel of Object.keys(this.channels)) { + this.channels[channel].channel = vscode.window.createOutputChannel( + this.channels[channel].name, + { log: true }, + ); + } + } + + /** + * Starts a WebSocket connection for a specific log type. + * @param logType - The type of log (e.g., 'main', 'detect', 'refactor'). + */ + private startWebSocket(logType: string): void { + const url = `${WEBSOCKET_BASE_URL}/${logType}`; + const ws = new WebSocket(url); + this.websockets[logType] = ws; + + ws.on('message', (data) => { + const logEvent = data.toString('utf8'); + const level = + logEvent.match(/\b(ERROR|DEBUG|INFO|WARNING|TRACE)\b/i)?.[0].trim() || + 'UNKNOWN'; + const msg = logEvent.split(`[${level}]`, 2)[1].trim(); + + switch (level) { + case 'ERROR': { + this.channels[logType].channel!.error(msg); + break; + } + case 'DEBUG': { + this.channels[logType].channel!.debug(msg); + break; + } + case 'WARNING': { + this.channels[logType].channel!.warn(msg); + break; + } + case 'CRITICAL': { + this.channels[logType].channel!.error(msg); + break; + } + default: { + this.channels[logType].channel!.info(msg); + break; + } + } + }); + + ws.on('error', (err) => { + this.channels[logType].channel!.error(`WebSocket error: ${err.message}`); + }); + + ws.on('close', () => { + this.channels[logType].channel!.appendLine( + `WebSocket connection closed for ${this.channels[logType].name}`, + ); + }); + + ws.on('open', () => { + this.channels[logType].channel!.appendLine( + `Connected to ${logType} via WebSocket`, + ); + }); + } + + /** + * Stops watching logs and cleans up resources. + */ + public stopWatchingLogs(): void { + Object.values(this.websockets).forEach((ws) => ws?.close()); + Object.values(this.channels).forEach((channel) => channel.channel?.dispose()); + } +} diff --git a/src/commands/views/exportMetricsData.ts b/src/commands/views/exportMetricsData.ts new file mode 100644 index 0000000..8fbb512 --- /dev/null +++ b/src/commands/views/exportMetricsData.ts @@ -0,0 +1,76 @@ +import * as vscode from 'vscode'; +import { dirname } from 'path'; +import { writeFileSync } from 'fs'; + +import { MetricsDataItem } from '../../providers/MetricsViewProvider'; +import { envConfig } from '../../utils/envConfig'; + +/** + * Exports collected metrics data to a JSON file in the workspace. + * Handles both file and directory workspace paths, saving the output + * as 'metrics-data.json' in the appropriate location. + * + * @param context - Extension context containing metrics data and workspace state + */ +export async function exportMetricsData( + context: vscode.ExtensionContext, +): Promise { + // Retrieve stored metrics data from extension context + const metricsData = context.workspaceState.get<{ + [path: string]: MetricsDataItem; + }>(envConfig.WORKSPACE_METRICS_DATA!, {}); + + console.log('metrics data:', metricsData); + + // Early return if no data available + if (Object.keys(metricsData).length === 0) { + vscode.window.showInformationMessage('No metrics data available to export.'); + return; + } + + // Get configured workspace path from extension context + const configuredWorkspacePath = context.workspaceState.get( + envConfig.WORKSPACE_CONFIGURED_PATH!, + ); + + console.log('configured path:', configuredWorkspacePath); + + if (!configuredWorkspacePath) { + vscode.window.showErrorMessage('No configured workspace path found.'); + return; + } + + // Determine output file location based on workspace type + const workspaceUri = vscode.Uri.file(configuredWorkspacePath); + let fileUri: vscode.Uri; + + try { + const stat = await vscode.workspace.fs.stat(workspaceUri); + + if (stat.type === vscode.FileType.Directory) { + // For directories, save directly in the workspace root + fileUri = vscode.Uri.joinPath(workspaceUri, 'metrics-data.json'); + } else if (stat.type === vscode.FileType.File) { + // For single files, save in the parent directory + const parentDir = vscode.Uri.file(dirname(configuredWorkspacePath)); + fileUri = vscode.Uri.joinPath(parentDir, 'metrics-data.json'); + } else { + vscode.window.showErrorMessage('Invalid workspace path type.'); + return; + } + } catch (error) { + vscode.window.showErrorMessage(`Failed to access workspace path: ${error}`); + return; + } + + // Write the metrics data to JSON file + try { + const jsonData = JSON.stringify(metricsData, null, 2); + writeFileSync(fileUri.fsPath, jsonData, 'utf-8'); + vscode.window.showInformationMessage( + `Metrics data exported successfully to ${fileUri.fsPath}`, + ); + } catch (error) { + vscode.window.showErrorMessage(`Failed to export metrics data: ${error}`); + } +} diff --git a/src/commands/views/filterSmells.ts b/src/commands/views/filterSmells.ts new file mode 100644 index 0000000..7a97c8f --- /dev/null +++ b/src/commands/views/filterSmells.ts @@ -0,0 +1,79 @@ +import * as vscode from 'vscode'; + +import { FilterViewProvider } from '../../providers/FilterViewProvider'; + +/** + * Registers VS Code commands for managing smell filters. + * @param context - The VS Code extension context. + * @param filterSmellsProvider - The provider responsible for handling smell filtering. + */ +export function registerFilterSmellCommands( + context: vscode.ExtensionContext, + filterSmellsProvider: FilterViewProvider, +): void { + /** + * Toggles the state of a specific smell filter. + */ + context.subscriptions.push( + vscode.commands.registerCommand( + 'ecooptimizer.toggleSmellFilter', + (smellKey: string) => { + filterSmellsProvider.toggleSmell(smellKey); + }, + ), + ); + + /** + * Edits a specific smell filter option. + * Prompts the user for input, validates the value, and updates the setting. + */ + context.subscriptions.push( + vscode.commands.registerCommand( + 'ecooptimizer.editSmellFilterOption', + async (item: any) => { + if (!item || !item.smellKey || !item.optionKey) { + vscode.window.showErrorMessage('Error: Missing smell or option key.'); + return; + } + + const { smellKey, optionKey, value: oldValue } = item; + + const newValue = await vscode.window.showInputBox({ + prompt: `Enter a new value for ${optionKey}`, + value: oldValue?.toString() || '', + validateInput: (input) => + isNaN(Number(input)) ? 'Must be a number' : undefined, + }); + + if (newValue !== undefined && !isNaN(Number(newValue))) { + filterSmellsProvider.updateOption(smellKey, optionKey, Number(newValue)); + filterSmellsProvider.refresh(); + } + }, + ), + ); + + /** + * Enables all smell filters. + */ + context.subscriptions.push( + vscode.commands.registerCommand('ecooptimizer.selectAllFilterSmells', () => { + filterSmellsProvider.setAllSmellsEnabled(true); + }), + ); + + /** + * Disables all smell filters. + */ + context.subscriptions.push( + vscode.commands.registerCommand('ecooptimizer.deselectAllFilterSmells', () => { + filterSmellsProvider.setAllSmellsEnabled(false); + }), + ); + + context.subscriptions.push( + vscode.commands.registerCommand('ecooptimizer.setFilterDefaults', () => { + filterSmellsProvider.resetToDefaults(); + }), + ); +} diff --git a/src/commands/views/jumpToSmell.ts b/src/commands/views/jumpToSmell.ts new file mode 100644 index 0000000..7946047 --- /dev/null +++ b/src/commands/views/jumpToSmell.ts @@ -0,0 +1,35 @@ +import * as vscode from 'vscode'; + +/** + * Jumps to a specific line in the given file within the VS Code editor. + * @param filePath - The absolute path of the file. + * @param line - The line number to navigate to. + */ +export async function jumpToSmell(filePath: string, line: number): Promise { + try { + const document = await vscode.workspace.openTextDocument(filePath); + const editor = await vscode.window.showTextDocument(document); + + // Move cursor to the specified line + const position = new vscode.Position(line, 0); + editor.selection = new vscode.Selection(position, position); + + const range = new vscode.Range(position, position); + editor.revealRange(range, vscode.TextEditorRevealType.InCenter); + + const flashDecorationType = vscode.window.createTextEditorDecorationType({ + backgroundColor: new vscode.ThemeColor('editor.wordHighlightBackground'), + isWholeLine: true, + }); + + editor.setDecorations(flashDecorationType, [range]); + + setTimeout(() => { + editor.setDecorations(flashDecorationType, []); + }, 500); + } catch (error: any) { + vscode.window.showErrorMessage( + `Failed to jump to smell in ${filePath}: ${error.message}`, + ); + } +} diff --git a/src/commands/views/openFile.ts b/src/commands/views/openFile.ts new file mode 100644 index 0000000..47687bf --- /dev/null +++ b/src/commands/views/openFile.ts @@ -0,0 +1,21 @@ +import * as vscode from 'vscode'; + +/** + * Opens a file in the VS Code editor. + * Ensures the file is fully opened (not in preview mode). + * Displays an error message if no file is selected. + * + * @param fileUri - The URI of the file to be opened. + */ +export async function openFile(fileUri: vscode.Uri) { + if (!fileUri) { + vscode.window.showErrorMessage('Error: No file selected.'); + return; + } + + await vscode.window.showTextDocument(fileUri, { + preview: false, // Ensures the file opens as a permanent tab (not in preview mode) + viewColumn: vscode.ViewColumn.Active, // Opens in the active editor column + preserveFocus: false, // Focuses the file when opened + }); +} diff --git a/src/context/SmellsCacheManager.ts b/src/context/SmellsCacheManager.ts new file mode 100644 index 0000000..4eb69e4 --- /dev/null +++ b/src/context/SmellsCacheManager.ts @@ -0,0 +1,194 @@ +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import { createHash } from 'crypto'; +import { envConfig } from '../utils/envConfig'; +import { ecoOutput } from '../extension'; +import { normalizePath } from '../utils/normalizePath'; + +/** + * Manages caching of detected smells to avoid redundant backend calls. + * Uses workspace storage to persist cache between sessions. + * Implements file content hashing for change detection and maintains + * a bidirectional mapping between file paths and their content hashes. + */ +export class SmellsCacheManager { + // Event emitter for cache update notifications + private cacheUpdatedEmitter = new vscode.EventEmitter(); + public readonly onSmellsUpdated = this.cacheUpdatedEmitter.event; + + constructor(private context: vscode.ExtensionContext) {} + + /** + * Generates a stable identifier for a smell based on its properties + * @param smell - The smell object to generate ID for + * @returns Short SHA-256 hash (first 5 chars) of the serialized smell + */ + private generateSmellId(smell: Smell): string { + return createHash('sha256') + .update(JSON.stringify(smell)) + .digest('hex') + .substring(0, 5); + } + + /** + * Generates content hash for a file to detect changes + * @param filePath - Absolute path to the file + * @returns SHA-256 hash of file content + */ + private generateFileHash(filePath: string): string { + const content = fs.readFileSync(filePath, 'utf-8'); + return createHash('sha256').update(content).digest('hex'); + } + + /** + * Stores smells in cache for specified file + * @param filePath - File path to associate with smells + * @param smells - Array of smell objects to cache + */ + public async setCachedSmells(filePath: string, smells: Smell[]): Promise { + const cache = this.getFullSmellCache(); + const pathMap = this.getHashToPathMap(); + + const normalizedPath = normalizePath(filePath); + const fileHash = this.generateFileHash(normalizedPath); + + // Augment smells with stable identifiers + const smellsWithIds = smells.map((smell) => ({ + ...smell, + id: this.generateSmellId(smell), + })); + + cache[fileHash] = smellsWithIds; + pathMap[fileHash] = normalizedPath; + + await this.context.workspaceState.update(envConfig.SMELL_CACHE_KEY!, cache); + await this.context.workspaceState.update(envConfig.HASH_PATH_MAP_KEY!, pathMap); + + this.cacheUpdatedEmitter.fire(filePath); + } + + /** + * Retrieves cached smells for a file + * @param filePath - File path to look up in cache + * @returns Array of smells or undefined if not found + */ + public getCachedSmells(filePath: string): Smell[] | undefined { + const normalizedPath = normalizePath(filePath); + const fileHash = this.generateFileHash(normalizedPath); + const cache = this.getFullSmellCache(); + return cache[fileHash]; + } + + /** + * Checks if smells exist in cache for a file + * @param filePath - File path to check + * @returns True if file has cached smells + */ + public hasCachedSmells(filePath: string): boolean { + const normalizedPath = normalizePath(filePath); + const fileHash = this.generateFileHash(normalizedPath); + const cache = this.getFullSmellCache(); + return cache[fileHash] !== undefined; + } + + /** + * Clears cache for a file by its current content hash + * @param filePath - File path to clear from cache + */ + public async clearCachedSmellsForFile(filePath: string): Promise { + const normalizedPath = normalizePath(filePath); + const fileHash = this.generateFileHash(normalizedPath); + const cache = this.getFullSmellCache(); + const pathMap = this.getHashToPathMap(); + + delete cache[fileHash]; + delete pathMap[fileHash]; + + await this.context.workspaceState.update(envConfig.SMELL_CACHE_KEY!, cache); + await this.context.workspaceState.update(envConfig.HASH_PATH_MAP_KEY!, pathMap); + + this.cacheUpdatedEmitter.fire(normalizedPath); + } + + /** + * Clears cache for a file by path (regardless of current content hash) + * @param filePath - File path to clear from cache + */ + public async clearCachedSmellsByPath(filePath: string): Promise { + const pathMap = this.getHashToPathMap(); + const normalizedPath = normalizePath(filePath); + const hash = Object.keys(pathMap).find((h) => pathMap[h] === normalizedPath); + if (!hash) return; + + const cache = this.getFullSmellCache(); + delete cache[hash]; + delete pathMap[hash]; + + await this.context.workspaceState.update(envConfig.SMELL_CACHE_KEY!, cache); + await this.context.workspaceState.update(envConfig.HASH_PATH_MAP_KEY!, pathMap); + + this.cacheUpdatedEmitter.fire(normalizedPath); + } + + /** + * Retrieves complete smell cache + * @returns Object mapping file hashes to smell arrays + */ + public getFullSmellCache(): Record { + return this.context.workspaceState.get>( + envConfig.SMELL_CACHE_KEY!, + {}, + ); + } + + /** + * Retrieves hash-to-path mapping + * @returns Object mapping file hashes to original paths + */ + public getHashToPathMap(): Record { + return this.context.workspaceState.get>( + envConfig.HASH_PATH_MAP_KEY!, + {}, + ); + } + + /** + * Clears entire smell cache + */ + public async clearAllCachedSmells(): Promise { + await this.context.workspaceState.update(envConfig.SMELL_CACHE_KEY!, {}); + await this.context.workspaceState.update(envConfig.HASH_PATH_MAP_KEY!, {}); + + this.cacheUpdatedEmitter.fire('all'); + } + + /** + * Retrieves all file paths currently in cache + * @returns Array of cached file paths + */ + public getAllFilePaths(): string[] { + const map = this.context.workspaceState.get>( + envConfig.HASH_PATH_MAP_KEY!, + {}, + ); + return Object.values(map); + } + + /** + * Checks if a file has any cache entries (current or historical) + * @param filePath - File path to check + * @returns True if file exists in cache metadata + */ + public hasFileInCache(filePath: string): boolean { + const pathMap = this.getHashToPathMap(); + const normalizedPath = normalizePath(filePath); + const fileExistsInCache = Object.values(pathMap).includes(normalizedPath); + + ecoOutput.debug( + `[SmellCacheManager] Path existence check for ${normalizedPath}: ` + + `${fileExistsInCache ? 'EXISTS' : 'NOT FOUND'} in cache`, + ); + + return fileExistsInCache; + } +} diff --git a/src/context/configManager.ts b/src/context/configManager.ts new file mode 100644 index 0000000..bd3a216 --- /dev/null +++ b/src/context/configManager.ts @@ -0,0 +1,39 @@ +import * as vscode from 'vscode'; + +export class ConfigManager { + private static readonly CONFIG_SECTION = 'ecooptimizer.detection'; + + /** + * Get a specific configuration value. + * @param key The key of the configuration property. + * @param _default The default value to return if the configuration property is not found. + * @returns The value of the configuration property. + */ + public static get(key: string, _default: any = undefined): T { + const config = vscode.workspace.getConfiguration(this.CONFIG_SECTION); + return config.get(key, _default); + } + + /** + * Update a specific configuration value. + * @param key The key of the configuration property. + * @param value The new value to set. + * @param global Whether to update the global configuration or workspace configuration. + */ + public static async update( + key: string, + value: T, + global: boolean = false, + ): Promise { + const config = vscode.workspace.getConfiguration(this.CONFIG_SECTION); + await config.update(key, value, global); + } + + /** + * Get all configuration values under the ecooptimizer.detection section. + * @returns The entire configuration object. + */ + public static getAll(): vscode.WorkspaceConfiguration { + return vscode.workspace.getConfiguration(this.CONFIG_SECTION); + } +} diff --git a/src/emitters/serverStatus.ts b/src/emitters/serverStatus.ts new file mode 100644 index 0000000..60bd515 --- /dev/null +++ b/src/emitters/serverStatus.ts @@ -0,0 +1,71 @@ +import * as vscode from 'vscode'; +import { EventEmitter } from 'events'; +import { ecoOutput } from '../extension'; + +/** + * Represents possible server connection states + */ +export enum ServerStatusType { + UNKNOWN = 'unknown', // Initial state before first connection attempt + UP = 'up', // Server is available and responsive + DOWN = 'down', // Server is unreachable or unresponsive +} + +/** + * Tracks and manages backend server connection state with: + * - Status change detection + * - Appropriate user notifications + * - Event emission for dependent components + */ +class ServerStatus extends EventEmitter { + private status: ServerStatusType = ServerStatusType.UNKNOWN; + + /** + * Gets current server connection state + * @returns Current ServerStatusType + */ + getStatus(): ServerStatusType { + return this.status; + } + + /** + * Updates server status with change detection and notifications + * @param newStatus - Either UP or DOWN status + */ + setStatus(newStatus: ServerStatusType.UP | ServerStatusType.DOWN): void { + if (this.status !== newStatus) { + const previousStatus = this.status; + this.status = newStatus; + + // Log status transition + ecoOutput.trace( + `[serverStatus.ts] Server status changed from ${previousStatus} to ${newStatus}`, + ); + + // Handle status-specific notifications + if (newStatus === ServerStatusType.UP) { + if (previousStatus !== ServerStatusType.UNKNOWN) { + ecoOutput.info('[serverStatus.ts] Server connection re-established'); + vscode.window.showInformationMessage( + 'Backend server reconnected - full functionality restored', + { modal: false }, + ); + } + } else { + ecoOutput.info('[serverStatus.ts] Server connection lost'); + vscode.window.showWarningMessage( + 'Backend server unavailable - limited functionality', + { modal: false }, + ); + } + + // Notify subscribers + this.emit('change', newStatus); + } + } +} + +/** + * Singleton instance providing global server status management + */ +export const serverStatus = new ServerStatus(); diff --git a/src/extension.ts b/src/extension.ts index c6ff254..a04e98a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,33 +1,687 @@ -import * as vscode from "vscode"; -import { detectSmells } from "./commands/detectSmells"; -import { refactorSelectedSmell } from "./commands/refactorSmell"; -import { getEditor } from "./utils/editorUtils"; - -interface Smell { - line: number; // Known attribute - [key: string]: any; // Index signature for unknown properties +import * as vscode from 'vscode'; +import path from 'path'; + +// let port: number; + +// export function getApiPort(): number { +// return port; +// } + +// === Output Channel === +export const ecoOutput = vscode.window.createOutputChannel('Eco-Optimizer', { + log: true, +}); + +// === Smell Linting === +let smellLintingEnabled = false; + +export function isSmellLintingEnabled(): boolean { + return smellLintingEnabled; } -export function activate(context: vscode.ExtensionContext) { - console.log("Refactor Plugin activated"); +// === In-Built === +import { existsSync, promises } from 'fs'; + +// === Core Utilities === +import { envConfig } from './utils/envConfig'; +import { getNameByMessageId, loadSmells } from './utils/smellsData'; +import { initializeStatusesFromCache } from './utils/initializeStatusesFromCache'; +import { checkServerStatus } from './api/backend'; + +// === Context & View Providers === +import { SmellsCacheManager } from './context/SmellsCacheManager'; +import { + SmellsViewProvider, + SmellTreeItem, + TreeItem, +} from './providers/SmellsViewProvider'; +import { MetricsViewProvider } from './providers/MetricsViewProvider'; +import { FilterViewProvider } from './providers/FilterViewProvider'; +import { RefactoringDetailsViewProvider } from './providers/RefactoringDetailsViewProvider'; + +// === Commands === +import { configureWorkspace } from './commands/configureWorkspace'; +import { resetConfiguration } from './commands/resetConfiguration'; +import { + detectSmellsFile, + detectSmellsFolder, +} from './commands/detection/detectSmells'; +import { registerFilterSmellCommands } from './commands/views/filterSmells'; +import { jumpToSmell } from './commands/views/jumpToSmell'; +import { wipeWorkCache } from './commands/detection/wipeWorkCache'; +import { refactor, startRefactorSession } from './commands/refactor/refactor'; +import { acceptRefactoring } from './commands/refactor/acceptRefactoring'; +import { rejectRefactoring } from './commands/refactor/rejectRefactoring'; +import { exportMetricsData } from './commands/views/exportMetricsData'; + +// === Listeners & UI === +import { WorkspaceModifiedListener } from './listeners/workspaceModifiedListener'; +import { FileHighlighter } from './ui/fileHighlighter'; +import { LineSelectionManager } from './ui/lineSelectionManager'; +import { HoverManager } from './ui/hoverManager'; +import { + closeAllTrackedDiffEditors, + registerDiffEditor, +} from './utils/trackedDiffEditors'; +import { initializeRefactorActionButtons } from './utils/refactorActionButtons'; +import { LogManager } from './commands/showLogs'; + +// === Backend Server === +// import { ServerProcess } from './lib/processManager'; +// import { DependencyManager } from './lib/dependencyManager'; + +let backendLogManager: LogManager; +// let server: ServerProcess; + +export async function activate(context: vscode.ExtensionContext): Promise { + ecoOutput.info('Initializing Eco-Optimizer extension...'); + console.log('Initializing Eco-Optimizer extension...'); + + // === Install and Run Backend Server ==== + // if (!(await DependencyManager.ensureDependencies(context))) { + // vscode.window.showErrorMessage( + // 'Cannot run the extension without the ecooptimizer server. Deactivating extension.', + // ); + // } + + // server = new ServerProcess(context); + // try { + // port = await server.start(); + + // console.log(`Server started on port ${port}`); + // } catch (error) { + // vscode.window.showErrorMessage(`Failed to start server: ${error}`); + // } + + backendLogManager = new LogManager(context); + + // === Load Core Data === + loadSmells(); + + // === Start periodic backend status checks === + checkServerStatus(); + setInterval(checkServerStatus, 10000); + + // === Initialize Refactor Action Buttons === + initializeRefactorActionButtons(context); + + // === Initialize Managers & Providers === + const smellsCacheManager = new SmellsCacheManager(context); + const smellsViewProvider = new SmellsViewProvider(context); + const metricsViewProvider = new MetricsViewProvider(context); + const filterSmellsProvider = new FilterViewProvider( + context, + metricsViewProvider, + smellsCacheManager, + smellsViewProvider, + ); + const refactoringDetailsViewProvider = new RefactoringDetailsViewProvider(); + + initializeStatusesFromCache(context, smellsCacheManager, smellsViewProvider); + + // === Register Tree Views === + context.subscriptions.push( + vscode.window.createTreeView('ecooptimizer.smellsView', { + treeDataProvider: smellsViewProvider, + }), + vscode.window.createTreeView('ecooptimizer.metricsView', { + treeDataProvider: metricsViewProvider, + showCollapseAll: true, + }), + vscode.window.createTreeView('ecooptimizer.filterView', { + treeDataProvider: filterSmellsProvider, + showCollapseAll: true, + }), + vscode.window.createTreeView('ecooptimizer.refactorView', { + treeDataProvider: refactoringDetailsViewProvider, + }), + ); + + filterSmellsProvider.setTreeView( + vscode.window.createTreeView('ecooptimizer.filterView', { + treeDataProvider: filterSmellsProvider, + showCollapseAll: true, + }), + ); + + const workspaceConfigured = context.workspaceState.get( + envConfig.WORKSPACE_CONFIGURED_PATH!, + ); + vscode.commands.executeCommand( + 'setContext', + 'workspaceState.workspaceConfigured', + Boolean(workspaceConfigured), + ); + + // === Register Commands === + context.subscriptions.push( + // vscode.commands.registerCommand('ecooptimizer.startServer', async () => { + // port = await server.start(); + // }), + // vscode.commands.registerCommand('ecooptimizer.stopServer', async () => { + // server.dispose(); + // }), + vscode.commands.registerCommand('ecooptimizer.configureWorkspace', async () => { + await configureWorkspace(context); + smellsViewProvider.refresh(); + metricsViewProvider.refresh(); + }), + + vscode.commands.registerCommand('ecooptimizer.resetConfiguration', async () => { + const didReset = await resetConfiguration(context); + if (didReset) { + smellsCacheManager.clearAllCachedSmells(); + smellsViewProvider.clearAllStatuses(); + smellsViewProvider.refresh(); + metricsViewProvider.refresh(); + vscode.window.showInformationMessage( + 'Workspace configuration and analysis data have been reset.', + ); + } + }), + + vscode.commands.registerCommand('ecooptimizer.jumpToSmell', jumpToSmell), + + vscode.commands.registerCommand('ecooptimizer.wipeWorkCache', async () => { + await wipeWorkCache(smellsCacheManager, smellsViewProvider); + }), + + vscode.commands.registerCommand( + 'ecooptimizer.detectSmellsFile', + async (fileItem: TreeItem) => { + let filePath: string; + if (!fileItem) { + const allPythonFiles: vscode.QuickPickItem[] = []; + const folderPath = workspaceConfigured; + + if (!folderPath) { + vscode.window.showWarningMessage('No workspace configured.'); + return; + } + + const gatherPythonFiles = async (dirPath: string): Promise => { + const files = await vscode.workspace.fs.readDirectory( + vscode.Uri.file(dirPath), + ); + for (const [name, type] of files) { + const fullPath = path.join(dirPath, name); + if (type === vscode.FileType.File && name.endsWith('.py')) { + const relativePath = path.relative(folderPath, fullPath); + allPythonFiles.push({ + label: `${name}`, + description: `${path.dirname(relativePath) === '.' ? undefined : path.dirname(relativePath)}`, + iconPath: new vscode.ThemeIcon('symbol-file'), + }); + } else if (type === vscode.FileType.Directory) { + await gatherPythonFiles(fullPath); // Recursively gather Python files in subdirectories + } + } + }; + + const currentFile = vscode.window.activeTextEditor?.document.fileName; + if (currentFile && currentFile.endsWith('.py')) { + const relativePath = path.relative(folderPath, currentFile); + allPythonFiles.push({ + label: `${path.basename(currentFile)}`, + description: `${path.dirname(relativePath) === '.' ? undefined : path.dirname(relativePath)}`, + detail: 'Current File', + iconPath: new vscode.ThemeIcon('symbol-file'), + }); + + allPythonFiles.push({ + label: '───────────────', + kind: vscode.QuickPickItemKind.Separator, + }); + } + + await gatherPythonFiles(folderPath); + + if (allPythonFiles.length === 0) { + vscode.window.showWarningMessage( + 'No Python files found in the workspace.', + ); + return; + } + + const selectedFile = await vscode.window.showQuickPick(allPythonFiles, { + title: 'Select a Python file to analyze', + placeHolder: 'Choose a Python file from the workspace', + canPickMany: false, + }); + + if (!selectedFile) { + vscode.window.showWarningMessage('No file selected.'); + return; + } + + filePath = path.join( + folderPath, + selectedFile.description!, + selectedFile.label, + ); + } else { + if (!(fileItem instanceof vscode.TreeItem)) { + vscode.window.showWarningMessage('Invalid file item selected.'); + return; + } + filePath = fileItem.resourceUri!.fsPath; + if (!filePath) { + vscode.window.showWarningMessage('Please select a file to analyze.'); + return; + } + } + detectSmellsFile(filePath, smellsViewProvider, smellsCacheManager); + }, + ), + + vscode.commands.registerCommand( + 'ecooptimizer.detectSmellsFolder', + async (folderItem: vscode.TreeItem) => { + let folderPath: string; + if (!folderItem) { + if (!workspaceConfigured) { + vscode.window.showWarningMessage('No workspace configured.'); + return; + } + + const allDirectories: vscode.QuickPickItem[] = []; + const directoriesWithPythonFiles = new Set(); + + const gatherDirectories = async ( + dirPath: string, + relativePath = '', + ): Promise => { + const files = await vscode.workspace.fs.readDirectory( + vscode.Uri.file(dirPath), + ); + let hasPythonFile = false; + + for (const [name, type] of files) { + const fullPath = path.join(dirPath, name); + const newRelativePath = path.join(relativePath, name); + if (type === vscode.FileType.File && name.endsWith('.py')) { + hasPythonFile = true; + } else if (type === vscode.FileType.Directory) { + const subDirHasPythonFile = await gatherDirectories( + fullPath, + newRelativePath, + ); + if (subDirHasPythonFile) { + hasPythonFile = true; + } + } + } + + if (hasPythonFile) { + directoriesWithPythonFiles.add(dirPath); + const isDirectChild = relativePath.split(path.sep).length === 1; + allDirectories.push({ + label: `${path.basename(dirPath)}`, + description: isDirectChild ? undefined : path.dirname(relativePath), + iconPath: new vscode.ThemeIcon('folder'), + }); + } + + return hasPythonFile; + }; + + await gatherDirectories(workspaceConfigured); + + if (allDirectories.length === 0) { + vscode.window.showWarningMessage( + 'No directories with Python files found in the workspace.', + ); + return; + } + + const selectedDirectory = await vscode.window.showQuickPick( + allDirectories, + { + title: 'Select a directory to analyze', + placeHolder: 'Choose a directory with Python files from the workspace', + canPickMany: false, + }, + ); + + if (!selectedDirectory) { + vscode.window.showWarningMessage('No directory selected.'); + return; + } + + folderPath = path.join( + workspaceConfigured, + selectedDirectory.description + ? path.join( + selectedDirectory.description, + path.basename(selectedDirectory.label), + ) + : path.basename(selectedDirectory.label), + ); + } else { + if (!(folderItem instanceof vscode.TreeItem)) { + vscode.window.showWarningMessage('Invalid folder item selected.'); + return; + } + folderPath = folderItem.resourceUri!.fsPath; + } + detectSmellsFolder(folderPath, smellsViewProvider, smellsCacheManager); + }, + ), + + vscode.commands.registerCommand( + 'ecooptimizer.refactorSmell', + (item: SmellTreeItem | Smell) => { + let smell: Smell; + if (item instanceof SmellTreeItem) { + smell = item.smell; + } else { + smell = item; + } + if (!smell) { + vscode.window.showErrorMessage('No code smell detected for this item.'); + return; + } + refactor(smellsViewProvider, refactoringDetailsViewProvider, smell, context); + }, + ), + + vscode.commands.registerCommand( + 'ecooptimizer.refactorAllSmellsOfType', + async (item: TreeItem | { fullPath: string; smellType: string }) => { + let filePath = item.fullPath; + if (!filePath) { + vscode.window.showWarningMessage( + 'Unable to get file path for smell refactoring.', + ); + return; + } + + const cachedSmells = smellsCacheManager.getCachedSmells(filePath); + if (!cachedSmells || cachedSmells.length === 0) { + vscode.window.showInformationMessage('No smells detected in this file.'); + return; + } + + ecoOutput.info(`🟡 Found ${cachedSmells.length} smells in ${filePath}`); + + const uniqueMessageIds = new Set(); + for (const smell of cachedSmells) { + uniqueMessageIds.add(smell.messageId); + } + + let selectedSmell: string; + if (item instanceof TreeItem) { + const quickPickItems: vscode.QuickPickItem[] = Array.from( + uniqueMessageIds, + ).map((id) => { + const name = getNameByMessageId(id) ?? id; + return { + label: name, + description: id, + }; + }); + + const selected = await vscode.window.showQuickPick(quickPickItems, { + title: 'Select a smell type to refactor', + placeHolder: 'Choose the type of smell you want to refactor', + matchOnDescription: false, + matchOnDetail: false, + ignoreFocusOut: false, + canPickMany: false, + }); + + if (!selected) { + return; + } + selectedSmell = selected.description!; + } else { + selectedSmell = item.smellType; + } + + const firstSmell = cachedSmells.find( + (smell) => smell.messageId === selectedSmell, + ); - // Register Detect Smells Command - let detectSmellsCmd = vscode.commands.registerCommand("ecooptimizer-vs-code-plugin.detectSmells", async () => { - console.log("Command detectSmells triggered"); - detectSmells(context); + if (!firstSmell) { + vscode.window.showWarningMessage('No smells found for the selected type.'); + return; } + + ecoOutput.info( + `🔁 Triggering refactorAllSmellsOfType for: ${selectedSmell}`, + ); + + await refactor( + smellsViewProvider, + refactoringDetailsViewProvider, + firstSmell, + context, + true, // isRefactorAllOfType + ); + }, + ), + + vscode.commands.registerCommand('ecooptimizer.acceptRefactoring', async () => { + await acceptRefactoring( + context, + refactoringDetailsViewProvider, + metricsViewProvider, + smellsCacheManager, + smellsViewProvider, + ); + }), + + vscode.commands.registerCommand('ecooptimizer.rejectRefactoring', async () => { + await rejectRefactoring( + context, + refactoringDetailsViewProvider, + smellsViewProvider, + ); + }), + + vscode.commands.registerCommand( + 'ecooptimizer.openDiffEditor', + (originalFilePath: string, refactoredFilePath: string) => { + // Get the file name for the diff editor title + const fileName = path.basename(originalFilePath); + + // Show the diff editor with the updated title + const originalUri = vscode.Uri.file(originalFilePath); + const refactoredUri = vscode.Uri.file(refactoredFilePath); + vscode.commands.executeCommand( + 'vscode.diff', + originalUri, + refactoredUri, + `Refactoring Comparison (${fileName})`, + { + preview: false, + }, + ); + + registerDiffEditor(originalUri, refactoredUri); + }, + ), + + vscode.commands.registerCommand('ecooptimizer.exportMetricsData', () => { + exportMetricsData(context); + }), + + vscode.commands.registerCommand('ecooptimizer.metricsView.refresh', () => { + metricsViewProvider.refresh(); + }), + + vscode.commands.registerCommand('ecooptimizer.clearMetricsData', () => { + vscode.window + .showWarningMessage( + 'Clear all metrics data? This cannot be undone unless you have exported it.', + { modal: true }, + 'Clear', + 'Cancel', + ) + .then((selection) => { + if (selection === 'Clear') { + context.workspaceState.update( + envConfig.WORKSPACE_METRICS_DATA!, + undefined, + ); + metricsViewProvider.refresh(); + vscode.window.showInformationMessage('Metrics data cleared.'); + } + }); + }), + ); + + // === Register Filter UI Commands === + registerFilterSmellCommands(context, filterSmellsProvider); + + // === Workspace File Listener === + context.subscriptions.push( + new WorkspaceModifiedListener( + context, + smellsCacheManager, + smellsViewProvider, + metricsViewProvider, + ), + ); + + // === File Highlighting === + const fileHighlighter = FileHighlighter.getInstance(smellsCacheManager); + + fileHighlighter.updateHighlightsForVisibleEditors(); + + // === Line Selection === + const lineSelectManager = new LineSelectionManager(smellsCacheManager); + context.subscriptions.push( + vscode.window.onDidChangeTextEditorSelection((event) => { + const textEditor = event.textEditor; + if (!textEditor.document.fileName.endsWith('.py')) { + return; + } + lineSelectManager.commentLine(textEditor); + }), + ); + + // == Hover Manager === + const hoverManager = new HoverManager(smellsCacheManager); + hoverManager.register(context); + + // === Smell Linting === + const updateSmellLintingContext = (): void => { + vscode.commands.executeCommand( + 'setContext', + 'ecooptimizer.smellLintingEnabled', + smellLintingEnabled, + ); + }; + + const lintActiveEditors = (): void => { + for (const editor of vscode.window.visibleTextEditors) { + const filePath = editor.document.uri.fsPath; + detectSmellsFile(filePath, smellsViewProvider, smellsCacheManager); + ecoOutput.info( + `[WorkspaceListener] Smell linting is ON — auto-detecting smells for ${filePath}`, + ); + } + }; + + const toggleSmellLinting = (): void => { + smellLintingEnabled = !smellLintingEnabled; + updateSmellLintingContext(); + const msg = smellLintingEnabled + ? 'Smell linting enabled' + : 'Smell linting disabled'; + lintActiveEditors(); + vscode.window.showInformationMessage(msg); + }; + + context.subscriptions.push( + vscode.commands.registerCommand( + 'ecooptimizer.toggleSmellLintingOn', + toggleSmellLinting, + ), + vscode.commands.registerCommand( + 'ecooptimizer.toggleSmellLintingOff', + toggleSmellLinting, + ), + ); + + // === File View Change Listner === + context.subscriptions.push( + vscode.window.onDidChangeVisibleTextEditors(() => { + fileHighlighter.updateHighlightsForVisibleEditors(); + + if (smellLintingEnabled) { + lintActiveEditors(); + } + }), + ); + + const cleanPastSessionArtifacts = async (): Promise => { + const pastData = context.workspaceState.get( + envConfig.UNFINISHED_REFACTORING!, ); - context.subscriptions.push(detectSmellsCmd); - - // Register Refactor Smell Command - let refactorSmellCmd = vscode.commands.registerCommand("ecooptimizer-vs-code-plugin.refactorSmell", () => { - console.log("Command refactorSmells triggered"); - vscode.window.showInformationMessage("Eco: Detecting smells..."); - refactorSelectedSmell(context); - }); - context.subscriptions.push(refactorSmellCmd); + + if (pastData) { + const tempDir = pastData.refactoredData.tempDir; + + try { + const tempDirExists = existsSync(tempDir); + + if (tempDirExists) { + const userChoice = await vscode.window.showWarningMessage( + 'A previous refactoring session was detected. Would you like to continue or discard it?', + { modal: true }, + 'Continue', + 'Discard', + ); + + if (userChoice === 'Discard') { + await promises.rm(tempDir, { recursive: true, force: true }); + + context.workspaceState.update( + envConfig.UNFINISHED_REFACTORING!, + undefined, + ); + + closeAllTrackedDiffEditors(); + } else if (userChoice === 'Continue') { + ecoOutput.info('Resuming previous refactoring session...'); + startRefactorSession( + pastData.smell, + pastData.refactoredData, + refactoringDetailsViewProvider, + ); + return; + } + } + } catch (error) { + ecoOutput.error(`Error handling past refactoring session: ${error}`); + context.workspaceState.update(envConfig.UNFINISHED_REFACTORING!, undefined); + } + } + }; + + cleanPastSessionArtifacts(); + + // if (!port) { + // try { + // port = await server.start(); + + // console.log(`Server started on port ${port}`); + // } catch (error) { + // vscode.window.showErrorMessage(`Failed to start server: ${error}`); + // } + // } + + ecoOutput.info('Eco-Optimizer extension activated successfully'); + console.log('Eco-Optimizer extension activated successfully'); } -export function deactivate() { - console.log("Refactor Plugin deactivated"); +export function deactivate(): void { + ecoOutput.info('Extension deactivated'); + console.log('Extension deactivated'); + + // server.dispose(); + backendLogManager.stopWatchingLogs(); + ecoOutput.dispose(); } diff --git a/src/global.d.ts b/src/global.d.ts index 3a437e4..3371cf5 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,39 +1,99 @@ export {}; -// global.d.ts -export {}; - +/** + * Global type declarations for the Eco-Optimizer extension. + * These interfaces define the core data structures used throughout the application. + */ declare global { - // Define your global types here - interface Occurrence { - line: number; - column: number; - call_string: string; - } - - interface Smell { - type: string; // Type of the smell (e.g., "performance", "convention") - symbol: string; // Symbolic identifier for the smell (e.g., "cached-repeated-calls") - message: string; // Detailed description of the smell - messageId: string; // Unique ID for the smell - line?: number; // Line number where the smell is detected - column?: number; // Column offset where the smell starts - endLine?: number; // Optional: Ending line for multiline smells - endColumn?: number; // Optional: Ending column for multiline smells - confidence: string; // Confidence level (e.g., "HIGH", "MEDIUM") - path?: string; // Relative file path - absolutePath?: string; // Absolute file pat. - module?: string; // Module name - obj?: string; // Object name associated with the smell (if applicable) - repetitions?: number; // Optional: Number of repeated occurrences - occurrences?: Occurrence[]; // Optional: List of occurrences for repeated calls - } - + /** + * Represents a specific location in source code where a smell occurs. + * Uses VS Code-style line/column numbering (1-based). + */ + export interface Occurrence { + /** The starting line number (1-based) */ + line: number; + /** The ending line number (1-based, optional) */ + endLine?: number; + /** The starting column number (1-based) */ + column: number; + /** The ending column number (1-based, optional) */ + endColumn?: number; + } + + /** + * Additional context-specific information about a code smell. + * The fields vary depending on the smell type. + */ + export interface AdditionalInfo { + // Fields for Cached Repeated Calls (CRC) smell: + /** Number of repetitions found (for CRC smells) */ + repetitions?: number; + /** The call string that's being repeated (for CRC smells) */ + callString?: string; + // Fields for String Concatenation in Loop (SCL) smell: + /** The target variable being concatenated (for SCL smells) */ + concatTarget?: string; + /** The line number where the inner loop occurs (for SCL smells) */ + innerLoopLine?: number; + } + + /** + * Represents a detected code smell with all its metadata. + * This is the core data structure for analysis results. + */ + export interface Smell { + /** Category of the smell (e.g., "performance", "convention") */ + type: string; + /** Unique identifier for the smell type (e.g., "cached-repeated-calls") */ + symbol: string; + /** Human-readable description of the smell */ + message: string; + /** Unique message ID for specific smell variations */ + messageId: string; + /** Confidence level in detection ("HIGH", "MEDIUM", "LOW") */ + confidence: string; + /** Absolute path to the file containing the smell */ + path: string; + /** Module or namespace where the smell was found */ + module: string; + /** Specific object/function name (when applicable) */ + obj?: string; + /** All detected locations of this smell in the code */ + occurences: Occurrence[]; + /** Type-specific additional information about the smell */ + additionalInfo: AdditionalInfo; + /** Unique identifier for this specific smell instance */ + id?: string; + } + + /** + * Represents the response from the backend refactoring service. + * Contains all necessary information to present and apply refactorings. + */ + export interface RefactoredData { + /** Temporary directory containing all refactored files */ + tempDir: string; + /** The main file that was refactored */ + targetFile: { + /** Path to the original version */ + original: string; + /** Path to the refactored version */ + refactored: string; + }; + /** Estimated energy savings in joules (optional) */ + energySaved?: number; + /** Any additional files affected by the refactoring */ + affectedFiles: { + /** Path to the original version */ + original: string; + /** Path to the refactored version */ + refactored: string; + }[]; + } - interface RefactorOutput { - refactored_code: string; // Refactored code as a string - energy_difference?: number; // Optional: energy difference (if provided) - updated_smells?: any[]; // Optional: updated smells (if provided) - } -} + export interface RefactorArtifacts { + refactoredData: RefactoredData; + smell: Smell; + } +} \ No newline at end of file diff --git a/src/install.ts b/src/install.ts new file mode 100644 index 0000000..f018d07 --- /dev/null +++ b/src/install.ts @@ -0,0 +1,310 @@ +import * as path from 'path'; +import * as childProcess from 'child_process'; +import { access, unlink, writeFile } from 'fs/promises'; + +// Constants for package management +const PACKAGE_NAME = 'ecooptimizer'; +const PYPI_INDEX = 'https://pypi.org/simple'; + +/** + * Configuration interface for Python environment setup + */ +interface InstallConfig { + pythonPath: string; + version: string; + targetDir: string; +} + +/** + * Ensures a valid Python virtual environment exists + * @param config Installation configuration + * @returns Path to the Python executable in the virtual environment + * @throws Error if environment setup fails + */ +async function ensurePythonEnvironment(config: InstallConfig): Promise { + const venvPath = path.join(config.targetDir, '.venv'); + const isWindows = process.platform === 'win32'; + const pythonExecutable = path.join( + venvPath, + isWindows ? 'Scripts' : 'bin', + isWindows ? 'python.exe' : 'python', + ); + + try { + // 1. Verify Python is available and executable + await new Promise((resolve, reject) => { + const pythonCheck = childProcess.spawn(config.pythonPath, ['--version']); + pythonCheck.stderr?.on('data', (chunk) => console.error(chunk)); + pythonCheck.stdout?.on('data', (chunk) => console.log(chunk)); + pythonCheck.on('close', (code) => { + code === 0 + ? resolve() + : reject(new Error(`Python check failed (code ${code})`)); + }); + pythonCheck.on('error', (err) => { + console.error(err); + reject(err); + }); + }); + + // 2. Check for existing virtual environment + let venvExists = false; + try { + await access(venvPath); + venvExists = true; + console.log('Virtual environment already exists'); + } catch { + console.log('Creating virtual environment...'); + } + + if (!venvExists) { + const tempFile = path.join(config.targetDir, 'create_venv_temp.py'); + try { + // Python script to create virtual environment + const scriptContent = ` +import sys +import venv +import os + +try: + venv.create("${venvPath.replace(/\\/g, '\\\\')}", + clear=True, + with_pip=True) + print("VENV_CREATION_SUCCESS") +except Exception as e: + print(f"VENV_CREATION_ERROR: {str(e)}", file=sys.stderr) + sys.exit(1) +`; + await writeFile(tempFile, scriptContent); + + const creationSuccess = await new Promise((resolve, reject) => { + const proc = childProcess.spawn(config.pythonPath, [tempFile], { + stdio: 'pipe', + }); + + let output = ''; + let errorOutput = ''; + + proc.stdout.on('data', (data) => (output += data.toString())); + proc.stderr.on('data', (data) => (errorOutput += data.toString())); + + proc.on('close', (code) => { + if (code === 0 && output.includes('VENV_CREATION_SUCCESS')) { + resolve(true); + } else { + const errorMatch = errorOutput.match(/VENV_CREATION_ERROR: (.+)/); + const errorMessage = + errorMatch?.[1] || `Process exited with code ${code}`; + console.error('Virtual environment creation failed:', errorMessage); + reject(new Error(errorMessage)); + } + }); + + proc.on('error', (err) => { + console.error('Process error:', err); + reject(err); + }); + }); + + // Fallback check if venv was partially created + if (!creationSuccess) { + try { + await access(pythonExecutable); + console.warn('Using partially created virtual environment'); + } catch (accessError) { + console.error( + 'Partial virtual environment creation failed:', + accessError, + ); + throw new Error('Virtual environment creation completely failed'); + } + } + } finally { + // Clean up temporary file + await unlink(tempFile).catch(() => {}); + } + } + + // 3. Final verification of virtual environment Python + await access(pythonExecutable); + return pythonExecutable; + } catch (error: any) { + console.error('Error in ensurePythonEnvironment:', error.message); + throw error; + } +} + +/** + * Verifies installed package version matches expected version + * @param pythonPath Path to Python executable + * @param config Installation configuration + * @returns true if version matches + * @throws Error if version mismatch or package not found + */ +async function verifyPyPackage( + pythonPath: string, + config: InstallConfig, +): Promise { + console.log('Verifying python package version...'); + const installedVersion = childProcess + .execSync( + `"${pythonPath}" -c "import importlib.metadata; print(importlib.metadata.version('${PACKAGE_NAME}'))"`, + ) + .toString() + .trim(); + + if (installedVersion !== config.version) { + throw new Error( + `Version mismatch: Expected ${config.version}, got ${installedVersion}`, + ); + } + + console.log('Version match.'); + return true; +} + +/** + * Installs package from PyPI into virtual environment + * @param config Installation configuration + */ +async function installFromPyPI(config: InstallConfig): Promise { + let pythonPath: string; + try { + pythonPath = await ensurePythonEnvironment(config); + console.log('Python environment is ready at:', pythonPath); + } catch (error: any) { + console.error('Failed to set up Python environment:', error.message); + return; + } + const pipPath = pythonPath.replace('python', 'pip'); + + // Skip if already installed + if (await verifyPyPackage(pythonPath, config)) { + console.log('Package already installed.'); + return; + } + + // Update setuptools first + console.log('Installing setup tools...'); + try { + childProcess.execSync(`"${pipPath}" install --upgrade "setuptools>=45.0.0"`, { + stdio: 'inherit', + }); + } catch (error) { + console.warn('Could not update setuptools:', error); + } + + // Main package installation + console.log('Installing ecooptimizer...'); + try { + childProcess.execSync( + `"${pipPath}" install --index-url ${PYPI_INDEX} "${PACKAGE_NAME}==${config.version}"`, + { stdio: 'inherit' }, + ); + + verifyPyPackage(pythonPath, config); + console.log('✅ Installation completed successfully'); + } catch (error) { + console.error('❌ Installation failed:', error); + throw error; + } +} + +/** + * Finds a valid Python executable path + * @returns Path to Python executable + * @throws Error if no valid Python found + */ +async function findPythonPath(): Promise { + // Check explicit environment variable first + if (process.env.PYTHON_PATH && (await validatePython(process.env.PYTHON_PATH))) { + return process.env.PYTHON_PATH; + } + + // Common Python executable names (ordered by preference) + const candidates = ['python', 'python3.10', 'python3', 'py']; + + // Platform-specific locations + if (process.platform === 'win32') { + candidates.push( + path.join( + process.env.LOCALAPPDATA || '', + 'Programs', + 'Python', + 'Python310', + 'python.exe', + ), + path.join(process.env.ProgramFiles || '', 'Python310', 'python.exe'), + ); + } + + if (process.platform === 'darwin') { + candidates.push('/usr/local/bin/python3'); // Homebrew default + } + + // Check environment-specific paths + if (process.env.CONDA_PREFIX) { + candidates.push(path.join(process.env.CONDA_PREFIX, 'bin', 'python')); + } + + if (process.env.VIRTUAL_ENV) { + candidates.push(path.join(process.env.VIRTUAL_ENV, 'bin', 'python')); + } + + // Test each candidate + for (const candidate of candidates) { + try { + if (await validatePython(candidate)) { + return candidate; + } + } catch { + continue; + } + } + + throw new Error('No valid Python installation found'); +} + +/** + * Validates Python executable meets requirements + * @param pythonPath Path to Python executable + * @returns true if valid Python 3.9+ installation + */ +async function validatePython(pythonPath: string): Promise { + try { + const versionOutput = childProcess + .execSync(`"${pythonPath}" --version`) + .toString() + .trim(); + + const versionMatch = versionOutput.match(/Python (\d+)\.(\d+)/); + if (!versionMatch) return false; + + const major = parseInt(versionMatch[1]); + const minor = parseInt(versionMatch[2]); + + console.log('Python version:', major, minor); + return major === 3 && minor >= 9; // Require Python 3.9+ + } catch { + return false; + } +} + +// Main execution block when run directly +if (require.main === module) { + (async (): Promise => { + try { + const config: InstallConfig = { + pythonPath: await findPythonPath(), + version: require('../package.json').version, + targetDir: process.cwd(), + }; + + console.log(`Using Python at: ${config.pythonPath}`); + await installFromPyPI(config); + } catch (error) { + console.error('Fatal error:', error instanceof Error ? error.message : error); + process.exit(1); + } + })(); +} diff --git a/src/lib/README.md b/src/lib/README.md new file mode 100644 index 0000000..94ac452 --- /dev/null +++ b/src/lib/README.md @@ -0,0 +1,3 @@ +The files in this folder do not currently do anything in the extension. + +They are left here for the future when the server backend can be properly integrated into the extension. \ No newline at end of file diff --git a/src/lib/dependencyManager.ts b/src/lib/dependencyManager.ts new file mode 100644 index 0000000..a388d6e --- /dev/null +++ b/src/lib/dependencyManager.ts @@ -0,0 +1,77 @@ +import { existsSync } from 'fs'; +import { join } from 'path'; +import * as vscode from 'vscode'; +import childProcess from 'child_process'; + +/** + * Handles Python dependency management for the extension. + * Creates and manages a virtual environment (.venv) in the extension directory + * and provides installation capabilities when dependencies are missing. + */ +export class DependencyManager { + /** + * Ensures required dependencies are installed. Checks for existing virtual environment + * and prompts user to install if missing. + * + * @param context - Extension context containing installation path + * @returns Promise resolving to true if dependencies are available, false otherwise + */ + static async ensureDependencies( + context: vscode.ExtensionContext, + ): Promise { + // Check for existing virtual environment + const venvPath = join(context.extensionPath, '.venv'); + if (existsSync(venvPath)) return true; + + // Prompt user to install dependencies if venv doesn't exist + const choice = await vscode.window.showErrorMessage( + 'Python dependencies missing. Install now?', + 'Install', + 'Cancel', + ); + + if (choice === 'Install') { + return vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: 'Installing dependencies...', + }, + async () => { + try { + await this.runInstaller(context); + return true; + } catch (error) { + vscode.window.showErrorMessage(`Installation failed: ${error}`); + return false; + } + }, + ); + } + return false; + } + + /** + * Executes the dependency installation process in a child process. + * Shows progress to user and handles installation errors. + * + * @param context - Extension context containing installation path + * @throws Error when installation process fails + */ + private static async runInstaller( + context: vscode.ExtensionContext, + ): Promise { + return new Promise((resolve, reject) => { + // Spawn installer process with inherited stdio for live output + const installer = childProcess.spawn('node', ['dist/install.js'], { + cwd: context.extensionPath, + stdio: 'inherit', // Show installation progress in parent console + }); + + installer.on('close', (code) => + code === 0 + ? resolve() + : reject(new Error(`Installer exited with code ${code}`)), + ); + }); + } +} diff --git a/src/lib/processManager.ts b/src/lib/processManager.ts new file mode 100644 index 0000000..37f66eb --- /dev/null +++ b/src/lib/processManager.ts @@ -0,0 +1,134 @@ +import * as childProcess from 'child_process'; +import { existsSync } from 'fs'; +import * as net from 'net'; +import { join } from 'path'; +import * as vscode from 'vscode'; +import { ecoOutput } from '../extension'; + +/** + * Manages the lifecycle of the backend server process, including: + * - Starting the Python server with proper environment + * - Port allocation and verification + * - Process cleanup on exit + * - Logging and error handling + */ +export class ServerProcess { + private process?: childProcess.ChildProcess; + + constructor(private context: vscode.ExtensionContext) {} + + /** + * Starts the backend server process and verifies it's ready. + * @returns Promise resolving to the port number the server is running on + * @throws Error if server fails to start or Python environment is missing + */ + async start(): Promise { + // Determine Python executable path based on platform + const pythonPath = join( + this.context.extensionPath, + process.platform === 'win32' + ? '.venv\\Scripts\\python.exe' + : '.venv/bin/python', + ); + + if (!existsSync(pythonPath)) { + throw new Error('Python environment not found'); + } + + // Clean up any existing server process + await this.killProcessTree(); + + // Find and bind to an available port + const port = await this.findFreePort(); + + // Start the Python server process + this.process = childProcess.spawn( + pythonPath, + ['-m', 'ecooptimizer.api', '--port', port.toString(), '--dev'], + { + cwd: this.context.extensionPath, + env: { ...process.env, PYTHONUNBUFFERED: '1' }, // Ensure unbuffered output + }, + ); + + // Set up process event handlers + this.process.stdout?.on('data', (data) => ecoOutput.info(`[Server] ${data}`)); + this.process.stderr?.on('data', (data) => ecoOutput.error(`[Server] ${data}`)); + this.process.on('close', () => { + ecoOutput.info('Server stopped'); + console.log('Server stopped'); + }); + + // Verify server is actually listening before returning + await this.verifyReady(port); + return port; + } + + /** + * Finds an available network port + * @returns Promise resolving to an available port number + */ + private async findFreePort(): Promise { + return new Promise((resolve) => { + const server = net.createServer(); + server.listen(0, () => { + const port = (server.address() as net.AddressInfo).port; + server.close(() => resolve(port)); + }); + }); + } + + /** + * Kills the server process and its entire process tree + * Handles platform-specific process termination + */ + private async killProcessTree(): Promise { + if (!this.process?.pid) return; + + try { + if (process.platform === 'win32') { + // Windows requires taskkill for process tree termination + childProcess.execSync(`taskkill /PID ${this.process.pid} /T /F`); + } else { + // Unix systems can kill process groups with negative PID + process.kill(-this.process.pid, 'SIGKILL'); + } + } catch (error) { + ecoOutput.error(`Process cleanup failed: ${error}`); + } finally { + this.process = undefined; + } + } + + /** + * Verifies the server is actually listening on the specified port + * @param port Port number to check + * @param timeout Maximum wait time in milliseconds + * @throws Error if server doesn't become ready within timeout + */ + private async verifyReady(port: number, timeout = 10000): Promise { + const start = Date.now(); + while (Date.now() - start < timeout) { + try { + const socket = net.createConnection({ port }); + await new Promise((resolve, reject) => { + socket.on('connect', resolve); + socket.on('error', reject); + }); + socket.end(); + return; + } catch { + // Retry after short delay if connection fails + await new Promise((resolve) => setTimeout(resolve, 200)); + } + } + throw new Error(`Server didn't start within ${timeout}ms`); + } + + /** + * Clean up resources when disposing of the manager + */ + dispose(): void { + this.process?.kill(); + } +} diff --git a/src/listeners/workspaceModifiedListener.ts b/src/listeners/workspaceModifiedListener.ts new file mode 100644 index 0000000..14eb386 --- /dev/null +++ b/src/listeners/workspaceModifiedListener.ts @@ -0,0 +1,201 @@ +import * as vscode from 'vscode'; +import { basename } from 'path'; + +import { SmellsCacheManager } from '../context/SmellsCacheManager'; +import { SmellsViewProvider } from '../providers/SmellsViewProvider'; +import { MetricsViewProvider } from '../providers/MetricsViewProvider'; +import { ecoOutput, isSmellLintingEnabled } from '../extension'; +import { detectSmellsFile } from '../commands/detection/detectSmells'; +import { envConfig } from '../utils/envConfig'; + +/** + * Monitors workspace modifications and maintains analysis state consistency by: + * - Tracking file system changes (create/change/delete) + * - Handling document save events + * - Managing cache invalidation + * - Coordinating view updates + */ +export class WorkspaceModifiedListener { + private fileWatcher: vscode.FileSystemWatcher | undefined; + private saveListener: vscode.Disposable | undefined; + + constructor( + private context: vscode.ExtensionContext, + private smellsCacheManager: SmellsCacheManager, + private smellsViewProvider: SmellsViewProvider, + private metricsViewProvider: MetricsViewProvider, + ) { + this.initializeFileWatcher(); + this.initializeSaveListener(); + ecoOutput.trace( + '[WorkspaceListener] Initialized workspace modification listener', + ); + } + + /** + * Creates file system watcher for Python files in configured workspace + */ + private initializeFileWatcher(): void { + const configuredPath = this.context.workspaceState.get( + envConfig.WORKSPACE_CONFIGURED_PATH!, + ); + if (!configuredPath) { + ecoOutput.trace( + '[WorkspaceListener] No workspace configured - skipping file watcher', + ); + return; + } + + try { + this.fileWatcher = vscode.workspace.createFileSystemWatcher( + new vscode.RelativePattern(configuredPath, '**/*.py'), + false, // Watch create events + false, // Watch change events + false, // Watch delete events + ); + + this.fileWatcher.onDidCreate(() => { + ecoOutput.trace('[WorkspaceListener] Detected new Python file'); + this.refreshViews(); + }); + + this.fileWatcher.onDidDelete((uri) => { + ecoOutput.trace(`[WorkspaceListener] Detected deletion of ${uri.fsPath}`); + this.handleFileDeletion(uri.fsPath); + }); + + ecoOutput.trace( + `[WorkspaceListener] Watching Python files in ${configuredPath}`, + ); + } catch (error) { + ecoOutput.error( + `[WorkspaceListener] Error initializing file watcher: ${error instanceof Error ? error.message : String(error)}`, + ); + } + } + + /** + * Sets up document save listener for Python files + */ + private initializeSaveListener(): void { + this.saveListener = vscode.workspace.onDidSaveTextDocument((document) => { + if (document.languageId === 'python') { + ecoOutput.trace( + `[WorkspaceListener] Detected save in ${document.uri.fsPath}`, + ); + this.handleFileChange(document.uri.fsPath); + + if (isSmellLintingEnabled()) { + ecoOutput.info( + `[WorkspaceListener] Smell linting is ON — auto-detecting smells for ${document.uri.fsPath}`, + ); + detectSmellsFile( + document.uri.fsPath, + this.smellsViewProvider, + this.smellsCacheManager, + ); + } + } + }); + } + + /** + * Handles file modifications by: + * - Invalidating cached analysis if exists + * - Marking file as outdated in UI + * @param filePath - Absolute path to modified file + */ + private async handleFileChange(filePath: string): Promise { + // Log current cache state for debugging + const cachedFiles = this.smellsCacheManager.getAllFilePaths(); + ecoOutput.trace( + `[WorkspaceListener] Current cached files (${cachedFiles.length}):\n` + + cachedFiles.map((f) => ` - ${f}`).join('\n'), + ); + + const hadCache = this.smellsCacheManager.hasFileInCache(filePath); + if (!hadCache) { + ecoOutput.trace(`[WorkspaceListener] No cache to invalidate for ${filePath}`); + return; + } + + try { + await this.smellsCacheManager.clearCachedSmellsForFile(filePath); + this.smellsViewProvider.setStatus(filePath, 'outdated'); + + ecoOutput.trace( + `[WorkspaceListener] Invalidated cache for modified file: ${filePath}`, + ); + vscode.window.showInformationMessage( + `Analysis data marked outdated for ${basename(filePath)}`, + { modal: false }, + ); + + this.refreshViews(); + } catch (error) { + ecoOutput.error( + `[WorkspaceListener] Error handling file change: ${error instanceof Error ? error.message : String(error)}`, + ); + } + } + + /** + * Handles file deletions by: + * - Clearing related cache entries + * - Removing from UI views + * @param filePath - Absolute path to deleted file + */ + private async handleFileDeletion(filePath: string): Promise { + const hadCache = this.smellsCacheManager.hasCachedSmells(filePath); + let removed = false; + + if (hadCache) { + try { + await this.smellsCacheManager.clearCachedSmellsByPath(filePath); + removed = true; + ecoOutput.trace( + `[WorkspaceListener] Cleared cache for deleted file: ${filePath}`, + ); + } catch (error) { + ecoOutput.error( + `[WorkspaceListener] Error clearing cache: ${error instanceof Error ? error.message : String(error)}`, + ); + } + } + + const removedFromTree = this.smellsViewProvider.removeFile(filePath); + if (removedFromTree) { + removed = true; + ecoOutput.trace(`[WorkspaceListener] Removed from view: ${filePath}`); + } + + if (removed) { + vscode.window.showInformationMessage( + `Removed analysis data for deleted file: ${basename(filePath)}`, + { modal: false }, + ); + } + + this.refreshViews(); + } + + /** + * Triggers refresh of all dependent views + */ + private refreshViews(): void { + this.smellsViewProvider.refresh(); + this.metricsViewProvider.refresh(); + ecoOutput.trace('[WorkspaceListener] Refreshed all views'); + } + + /** + * Cleans up resources including: + * - File system watcher + * - Document save listener + */ + public dispose(): void { + this.fileWatcher?.dispose(); + this.saveListener?.dispose(); + ecoOutput.trace('[WorkspaceListener] Disposed all listeners'); + } +} diff --git a/src/providers/FilterViewProvider.ts b/src/providers/FilterViewProvider.ts new file mode 100644 index 0000000..cab9a17 --- /dev/null +++ b/src/providers/FilterViewProvider.ts @@ -0,0 +1,269 @@ +import * as vscode from 'vscode'; +import { + FilterSmellConfig, + getFilterSmells, + loadSmells, + saveSmells, +} from '../utils/smellsData'; +import { MetricsViewProvider } from './MetricsViewProvider'; +import { SmellsCacheManager } from '../context/SmellsCacheManager'; +import { SmellsViewProvider } from './SmellsViewProvider'; + +/** + * Provides a tree view for managing and filtering code smells in the VS Code extension. + * Handles smell configuration, option editing, and maintains consistency with cached results. + */ +export class FilterViewProvider implements vscode.TreeDataProvider { + // Event emitter for tree view updates + private _onDidChangeTreeData: vscode.EventEmitter< + vscode.TreeItem | undefined | void + > = new vscode.EventEmitter(); + readonly onDidChangeTreeData: vscode.Event = + this._onDidChangeTreeData.event; + + private treeView?: vscode.TreeView; + private smells: Record = {}; + + constructor( + private context: vscode.ExtensionContext, + private metricsViewProvider: MetricsViewProvider, + private smellsCacheManager: SmellsCacheManager, + private smellsViewProvider: SmellsViewProvider, + ) { + this.smells = getFilterSmells(); + } + + /** + * Sets up the tree view and handles checkbox state changes + * @param treeView The VS Code tree view instance to manage + */ + setTreeView(treeView: vscode.TreeView): void { + this.treeView = treeView; + + this.treeView.onDidChangeCheckboxState(async (event) => { + for (const [item] of event.items) { + if (item instanceof SmellItem) { + const confirmed = await this.confirmFilterChange(); + if (confirmed) { + await this.toggleSmell(item.key); + } else { + // Refresh view if change was cancelled + this._onDidChangeTreeData.fire(); + } + } + } + }); + } + + getTreeItem(element: vscode.TreeItem): vscode.TreeItem { + return element; + } + + /** + * Gets children items for the tree view + * @param element Parent element or undefined for root items + * @returns Promise resolving to array of tree items + */ + getChildren(element?: SmellItem): Thenable { + if (!element) { + // Root level items - all available smells + return Promise.resolve( + Object.keys(this.smells) + .sort((a, b) => this.smells[a].name.localeCompare(this.smells[b].name)) + .map((smellKey) => { + const smell = this.smells[smellKey]; + return new SmellItem( + smellKey, + smell.name, + smell.enabled, + smell.analyzer_options && + Object.keys(smell.analyzer_options).length > 0 + ? vscode.TreeItemCollapsibleState.Collapsed + : vscode.TreeItemCollapsibleState.None, + ); + }), + ); + } + + // Child items - smell configuration options + const options = this.smells[element.key]?.analyzer_options; + return options + ? Promise.resolve( + Object.entries(options).map( + ([optionKey, optionData]) => + new SmellOptionItem( + optionKey, + optionData.label, + optionData.value, + optionData.description, + element.key, + ), + ), + ) + : Promise.resolve([]); + } + + /** + * Toggles a smell's enabled state + * @param smellKey The key of the smell to toggle + */ + async toggleSmell(smellKey: string): Promise { + if (this.smells[smellKey]) { + this.smells[smellKey].enabled = !this.smells[smellKey].enabled; + saveSmells(this.smells); + await this.invalidateCachedSmellsForAffectedFiles(); + this._onDidChangeTreeData.fire(); + } + } + + /** + * Updates a smell analyzer option value + * @param smellKey The smell containing the option + * @param optionKey The option to update + * @param newValue The new value for the option + */ + async updateOption( + smellKey: string, + optionKey: string, + newValue: number | string, + ): Promise { + const confirmed = await this.confirmFilterChange(); + if (!confirmed) return; + + if (this.smells[smellKey]?.analyzer_options?.[optionKey]) { + this.smells[smellKey].analyzer_options[optionKey].value = newValue; + saveSmells(this.smells); + await this.invalidateCachedSmellsForAffectedFiles(); + this._onDidChangeTreeData.fire(); + } else { + vscode.window.showErrorMessage( + `Error: No analyzer option found for ${optionKey}`, + ); + } + } + + refresh(): void { + this._onDidChangeTreeData.fire(undefined); + } + + /** + * Enables or disables all smells at once + * @param enabled Whether to enable or disable all smells + */ + async setAllSmellsEnabled(enabled: boolean): Promise { + const confirmed = await this.confirmFilterChange(); + if (!confirmed) return; + + Object.keys(this.smells).forEach((key) => { + this.smells[key].enabled = enabled; + }); + saveSmells(this.smells); + await this.invalidateCachedSmellsForAffectedFiles(); + this._onDidChangeTreeData.fire(); + } + + /** + * Resets all smell configurations to their default values + */ + async resetToDefaults(): Promise { + const confirmed = await this.confirmFilterChange(); + if (!confirmed) return; + + loadSmells('default'); + this.smells = getFilterSmells(); + saveSmells(this.smells); + + await this.invalidateCachedSmellsForAffectedFiles(); + this._onDidChangeTreeData.fire(); + } + + /** + * Invalidates cached smells for all files when filters change + */ + async invalidateCachedSmellsForAffectedFiles(): Promise { + const cachedFilePaths = this.smellsCacheManager.getAllFilePaths(); + + for (const filePath of cachedFilePaths) { + this.smellsCacheManager.clearCachedSmellsForFile(filePath); + this.smellsViewProvider.setStatus(filePath, 'outdated'); + } + + this.metricsViewProvider.refresh(); + this.smellsViewProvider.refresh(); + } + + /** + * Shows confirmation dialog for filter changes that invalidate cache + * @returns Promise resolving to whether change should proceed + */ + private async confirmFilterChange(): Promise { + const suppressWarning = this.context.workspaceState.get( + 'ecooptimizer.suppressFilterWarning', + false, + ); + + if (suppressWarning) { + return true; + } + + const result = await vscode.window.showWarningMessage( + 'Changing smell filters will invalidate existing analysis results. Do you want to continue?', + { modal: true }, + 'Yes', + "Don't Remind Me Again", + ); + + if (result === "Don't Remind Me Again") { + await this.context.workspaceState.update( + 'ecooptimizer.suppressFilterWarning', + true, + ); + return true; + } + + return result === 'Yes'; + } +} + +/** + * Tree item representing a single smell in the filter view + */ +class SmellItem extends vscode.TreeItem { + constructor( + public readonly key: string, + public readonly name: string, + public enabled: boolean, + public readonly collapsibleState: vscode.TreeItemCollapsibleState, + ) { + super(name, collapsibleState); + this.contextValue = 'smellItem'; + this.checkboxState = enabled + ? vscode.TreeItemCheckboxState.Checked + : vscode.TreeItemCheckboxState.Unchecked; + } +} + +/** + * Tree item representing a configurable option for a smell + */ +class SmellOptionItem extends vscode.TreeItem { + constructor( + public readonly optionKey: string, + public readonly label: string, + public value: number | string, + public readonly description: string, + public readonly smellKey: string, + ) { + super('placeholder', vscode.TreeItemCollapsibleState.None); + + this.contextValue = 'smellOption'; + this.label = `${label}: ${value}`; + this.tooltip = description; + this.description = ''; + this.command = { + command: 'ecooptimizer.editSmellFilterOption', + title: 'Edit Option', + arguments: [this], + }; + } +} diff --git a/src/providers/MetricsViewProvider.ts b/src/providers/MetricsViewProvider.ts new file mode 100644 index 0000000..e003054 --- /dev/null +++ b/src/providers/MetricsViewProvider.ts @@ -0,0 +1,421 @@ +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import { basename, dirname } from 'path'; +import { buildPythonTree } from '../utils/TreeStructureBuilder'; +import { envConfig } from '../utils/envConfig'; +import { getFilterSmells } from '../utils/smellsData'; +import { normalizePath } from '../utils/normalizePath'; + +/** + * Custom TreeItem for displaying metrics in the VS Code explorer + * Handles different node types (folders, files, smells) with appropriate icons and behaviors + */ +class MetricTreeItem extends vscode.TreeItem { + constructor( + public readonly label: string, + public readonly collapsibleState: vscode.TreeItemCollapsibleState, + public readonly contextValue: string, + public readonly carbonSaved?: number, + public readonly resourceUri?: vscode.Uri, + public readonly smellName?: string, + ) { + super(label, collapsibleState); + + // Set icon based on node type + switch (this.contextValue) { + case 'folder': + this.iconPath = new vscode.ThemeIcon('folder'); + break; + case 'file': + this.iconPath = new vscode.ThemeIcon('file'); + break; + case 'smell': + this.iconPath = new vscode.ThemeIcon('tag'); + break; + case 'folder-stats': + this.iconPath = new vscode.ThemeIcon('graph'); + break; + } + + // Format carbon savings display + this.description = + carbonSaved !== undefined + ? `Carbon Saved: ${formatNumber(carbonSaved)} kg` + : ''; + this.tooltip = smellName || this.description; + + // Make files clickable to open them + if (resourceUri && contextValue === 'file') { + this.command = { + title: 'Open File', + command: 'vscode.open', + arguments: [resourceUri], + }; + } + } +} + +/** + * Interface for storing metrics data for individual files + */ +export interface MetricsDataItem { + totalCarbonSaved: number; + smellDistribution: { + [smell: string]: number; + }; +} + +/** + * Structure for aggregating metrics across folders + */ +interface FolderMetrics { + totalCarbonSaved: number; + smellDistribution: Map; // Map + children: { + files: Map; // Map + folders: Map; // Map + }; +} + +/** + * Provides a tree view of carbon savings metrics across the workspace + * Aggregates data by folder structure and smell types with caching for performance + */ +export class MetricsViewProvider implements vscode.TreeDataProvider { + private _onDidChangeTreeData = new vscode.EventEmitter< + MetricTreeItem | undefined + >(); + readonly onDidChangeTreeData = this._onDidChangeTreeData.event; + + // Cache for folder metrics to avoid repeated calculations + private folderMetricsCache: Map = new Map(); + + constructor(private context: vscode.ExtensionContext) {} + + /** + * Triggers a refresh of the tree view + */ + refresh(): void { + this._onDidChangeTreeData.fire(undefined); + } + + getTreeItem(element: MetricTreeItem): vscode.TreeItem { + return element; + } + + /** + * Builds the tree view hierarchy + * @param element The parent element or undefined for root items + * @returns Promise resolving to child tree items + */ + async getChildren(element?: MetricTreeItem): Promise { + const metricsData = this.context.workspaceState.get<{ + [path: string]: MetricsDataItem; + }>(envConfig.WORKSPACE_METRICS_DATA!, {}); + + // Root level items + if (!element) { + const configuredPath = this.context.workspaceState.get( + envConfig.WORKSPACE_CONFIGURED_PATH!, + ); + if (!configuredPath) return []; + + // Show either single file or folder contents at root + const isDirectory = + fs.existsSync(configuredPath) && fs.statSync(configuredPath).isDirectory(); + if (isDirectory) { + return [this.createFolderItem(configuredPath)]; + } else { + return [this.createFileItem(configuredPath, metricsData)]; + } + } + + // Folder contents + if (element.contextValue === 'folder') { + const folderPath = element.resourceUri!.fsPath; + const folderMetrics = await this.calculateFolderMetrics( + folderPath, + metricsData, + ); + const treeNodes = buildPythonTree(folderPath); + + // Create folder statistics section + const folderStats = [ + new MetricTreeItem( + `Total Carbon Saved: ${formatNumber(folderMetrics.totalCarbonSaved)} kg`, + vscode.TreeItemCollapsibleState.None, + 'folder-stats', + ), + ...Array.from(folderMetrics.smellDistribution.entries()).map( + ([acronym, [name, carbonSaved]]) => + this.createSmellItem({ acronym, name, carbonSaved }), + ), + ].sort(compareTreeItems); + + // Create folder contents listing + const contents = treeNodes.map((node) => { + return node.isFile + ? this.createFileItem(node.fullPath, metricsData) + : this.createFolderItem(node.fullPath); + }); + + return [...contents, ...folderStats]; + } + + // File smell breakdown + if (element.contextValue === 'file') { + const filePath = element.resourceUri!.fsPath; + const fileMetrics = this.calculateFileMetrics(filePath, metricsData); + return fileMetrics.smellData.map((data) => this.createSmellItem(data)); + } + + return []; + } + + /** + * Creates a folder tree item + */ + private createFolderItem(folderPath: string): MetricTreeItem { + return new MetricTreeItem( + basename(folderPath), + vscode.TreeItemCollapsibleState.Collapsed, + 'folder', + undefined, + vscode.Uri.file(folderPath), + ); + } + + /** + * Creates a file tree item with carbon savings + */ + private createFileItem( + filePath: string, + metricsData: { [path: string]: MetricsDataItem }, + ): MetricTreeItem { + const fileMetrics = this.calculateFileMetrics(filePath, metricsData); + return new MetricTreeItem( + basename(filePath), + vscode.TreeItemCollapsibleState.Collapsed, + 'file', + fileMetrics.totalCarbonSaved, + vscode.Uri.file(filePath), + ); + } + + /** + * Creates a smell breakdown item + */ + private createSmellItem(data: { + acronym: string; + name: string; + carbonSaved: number; + }): MetricTreeItem { + return new MetricTreeItem( + `${data.acronym}: ${formatNumber(data.carbonSaved)} kg`, + vscode.TreeItemCollapsibleState.None, + 'smell', + undefined, + undefined, + data.name, + ); + } + + /** + * Calculates aggregated metrics for a folder and its contents + * Uses caching to optimize performance for large folder structures + */ + private async calculateFolderMetrics( + folderPath: string, + metricsData: { [path: string]: MetricsDataItem }, + ): Promise { + // Return cached metrics if available + const cachedMetrics = this.folderMetricsCache.get(folderPath); + if (cachedMetrics) { + return cachedMetrics; + } + + const folderMetrics: FolderMetrics = { + totalCarbonSaved: 0, + smellDistribution: new Map(), + children: { + files: new Map(), + folders: new Map(), + }, + }; + + // Build directory tree structure + const treeNodes = buildPythonTree(folderPath); + + for (const node of treeNodes) { + if (node.isFile) { + // Aggregate file metrics + const fileMetrics = this.calculateFileMetrics(node.fullPath, metricsData); + folderMetrics.children.files.set( + node.fullPath, + fileMetrics.totalCarbonSaved, + ); + folderMetrics.totalCarbonSaved += fileMetrics.totalCarbonSaved; + + // Aggregate smell distribution from file + for (const smellData of fileMetrics.smellData) { + const current = + folderMetrics.smellDistribution.get(smellData.acronym)?.[1] || 0; + folderMetrics.smellDistribution.set(smellData.acronym, [ + smellData.name, + current + smellData.carbonSaved, + ]); + } + } else { + // Recursively process subfolders + const subFolderMetrics = await this.calculateFolderMetrics( + node.fullPath, + metricsData, + ); + folderMetrics.children.folders.set(node.fullPath, subFolderMetrics); + folderMetrics.totalCarbonSaved += subFolderMetrics.totalCarbonSaved; + + // Aggregate smell distribution from subfolder + subFolderMetrics.smellDistribution.forEach( + ([name, carbonSaved], acronym) => { + const current = folderMetrics.smellDistribution.get(acronym)?.[1] || 0; + folderMetrics.smellDistribution.set(acronym, [ + name, + current + carbonSaved, + ]); + }, + ); + } + } + + // Cache the calculated metrics + this.folderMetricsCache.set(folderPath, folderMetrics); + return folderMetrics; + } + + /** + * Calculates metrics for a single file + */ + private calculateFileMetrics( + filePath: string, + metricsData: { [path: string]: MetricsDataItem }, + ): { + totalCarbonSaved: number; + smellData: { acronym: string; name: string; carbonSaved: number }[]; + } { + const smellConfigData = getFilterSmells(); + const fileData = metricsData[normalizePath(filePath)] || { + totalCarbonSaved: 0, + smellDistribution: {}, + }; + + // Filter smell distribution to only include enabled smells + const smellDistribution = Object.keys(smellConfigData).reduce( + (acc, symbol) => { + if (smellConfigData[symbol]) { + acc[symbol] = fileData.smellDistribution[symbol] || 0; + } + return acc; + }, + {} as Record, + ); + + return { + totalCarbonSaved: fileData.totalCarbonSaved, + smellData: Object.entries(smellDistribution).map(([symbol, carbonSaved]) => ({ + acronym: smellConfigData[symbol].acronym, + name: smellConfigData[symbol].name, + carbonSaved, + })), + }; + } + + /** + * Updates metrics for a file when new analysis results are available + */ + updateMetrics(filePath: string, carbonSaved: number, smellSymbol: string): void { + const metrics = this.context.workspaceState.get<{ + [path: string]: MetricsDataItem; + }>(envConfig.WORKSPACE_METRICS_DATA!, {}); + + const normalizedPath = normalizePath(filePath); + + // Initialize metrics if they don't exist + if (!metrics[normalizedPath]) { + metrics[normalizedPath] = { + totalCarbonSaved: 0, + smellDistribution: {}, + }; + } + + // Update metrics + metrics[normalizedPath].totalCarbonSaved = + (metrics[normalizedPath].totalCarbonSaved || 0) + carbonSaved; + + if (!metrics[normalizedPath].smellDistribution[smellSymbol]) { + metrics[normalizedPath].smellDistribution[smellSymbol] = 0; + } + metrics[normalizedPath].smellDistribution[smellSymbol] += carbonSaved; + + // Persist changes + this.context.workspaceState.update(envConfig.WORKSPACE_METRICS_DATA!, metrics); + + // Clear cache for all parent folders + this.clearCacheForFileParents(filePath); + this.refresh(); + } + + /** + * Clears cached metrics for all parent folders of a modified file + */ + private clearCacheForFileParents(filePath: string): void { + let configuredPath = this.context.workspaceState.get( + envConfig.WORKSPACE_CONFIGURED_PATH!, + ); + + if (!configuredPath) { + return; + } + configuredPath = normalizePath(configuredPath); + + // Walk up the directory tree clearing cache + let currentPath = dirname(filePath); + while (currentPath.includes(configuredPath)) { + this.folderMetricsCache.delete(currentPath); + currentPath = dirname(currentPath); + } + } +} + +// =========================================================== +// HELPER FUNCTIONS +// =========================================================== + +/** + * Priority for sorting tree items by type + */ +const contextPriority: { [key: string]: number } = { + folder: 1, + file: 2, + smell: 3, + 'folder-stats': 4, +}; + +/** + * Comparator for tree items (folders first, then files, then smells) + */ +function compareTreeItems(a: MetricTreeItem, b: MetricTreeItem): number { + const priorityA = contextPriority[a.contextValue] || 0; + const priorityB = contextPriority[b.contextValue] || 0; + if (priorityA !== priorityB) return priorityA - priorityB; + return a.label.localeCompare(b.label); +} + +/** + * Formats numbers for display, using scientific notation for very small values + */ +function formatNumber(number: number, decimalPlaces: number = 2): string { + const threshold = 0.001; + return Math.abs(number) < threshold + ? number.toExponential(decimalPlaces) + : number.toFixed(decimalPlaces); +} diff --git a/src/providers/RefactoringDetailsViewProvider.ts b/src/providers/RefactoringDetailsViewProvider.ts new file mode 100644 index 0000000..d5c8b45 --- /dev/null +++ b/src/providers/RefactoringDetailsViewProvider.ts @@ -0,0 +1,201 @@ +import * as vscode from 'vscode'; +import * as path from 'path'; +import { getDescriptionByMessageId, getNameByMessageId } from '../utils/smellsData'; + +/** + * Provides a tree view that displays detailed information about ongoing refactoring operations. + * Shows the target smell, affected files, and estimated energy savings. + */ +export class RefactoringDetailsViewProvider + implements vscode.TreeDataProvider +{ + // Event emitter for tree data changes + private _onDidChangeTreeData = new vscode.EventEmitter< + RefactoringDetailItem | undefined + >(); + readonly onDidChangeTreeData = this._onDidChangeTreeData.event; + + // State properties + private refactoringDetails: RefactoringDetailItem[] = []; + public targetFile: { original: string; refactored: string } | undefined; + public affectedFiles: { original: string; refactored: string }[] = []; + public energySaved: number | undefined; + public targetSmell: Smell | undefined; + + constructor() { + this.resetRefactoringDetails(); + } + + /** + * Updates the view with new refactoring details + * @param targetSmell - The code smell being refactored + * @param targetFile - Paths to original and refactored target files + * @param affectedFiles - Additional files impacted by the refactoring + * @param energySaved - Estimated energy savings in kg CO2 + */ + updateRefactoringDetails( + targetSmell: Smell, + targetFile: { original: string; refactored: string }, + affectedFiles: { original: string; refactored: string }[], + energySaved: number | undefined, + ): void { + this.targetSmell = targetSmell; + this.targetFile = targetFile; + this.affectedFiles = affectedFiles; + this.energySaved = energySaved; + this.refactoringDetails = []; + + // Add smell information + if (targetSmell) { + const smellName = + getNameByMessageId(targetSmell.messageId) || targetSmell.messageId; + this.refactoringDetails.push( + new RefactoringDetailItem( + `Refactoring: ${smellName}`, + '', + '', + '', + true, + false, + true, + ), + ); + } + + // Add energy savings + if (energySaved !== undefined) { + this.refactoringDetails.push( + new RefactoringDetailItem( + `Estimated Savings: ${energySaved} kg CO2`, + 'Based on energy impact analysis', + '', + '', + false, + true, + ), + ); + } + + // Add target file + if (targetFile) { + this.refactoringDetails.push( + new RefactoringDetailItem( + `${path.basename(targetFile.original)}`, + 'Main refactored file', + targetFile.original, + targetFile.refactored, + affectedFiles.length > 0, + ), + ); + } + + this._onDidChangeTreeData.fire(undefined); + } + + /** + * Resets the view to its initial state + */ + resetRefactoringDetails(): void { + this.targetFile = undefined; + this.affectedFiles = []; + this.targetSmell = undefined; + this.energySaved = undefined; + this.refactoringDetails = []; + this._onDidChangeTreeData.fire(undefined); + } + + // VS Code TreeDataProvider implementation + getTreeItem(element: RefactoringDetailItem): vscode.TreeItem { + return element; + } + + getChildren(element?: RefactoringDetailItem): RefactoringDetailItem[] { + if (!element) { + return this.refactoringDetails; + } + + // Handle smell description expansion + if (element.isSmellItem && this.targetSmell) { + const description = + getDescriptionByMessageId(this.targetSmell.messageId) || + this.targetSmell.message; + return [ + new RefactoringDetailItem( + '', + description, + '', + '', + false, + false, + false, + 'info', + ), + ]; + } + + // Handle affected files expansion + if (element.isParent && this.affectedFiles.length > 0) { + return this.affectedFiles.map( + (file) => + new RefactoringDetailItem( + path.basename(file.original), + 'Affected file', + file.original, + file.refactored, + ), + ); + } + + return []; + } +} + +/** + * Represents an item in the refactoring details tree view + */ +class RefactoringDetailItem extends vscode.TreeItem { + constructor( + public readonly label: string, + public readonly description: string, + public readonly originalFilePath: string, + public readonly refactoredFilePath: string, + public readonly isParent: boolean = false, + public readonly isEnergySaved: boolean = false, + public readonly isSmellItem: boolean = false, + public readonly itemType: 'info' | 'file' | 'none' = 'none', + ) { + super( + label, + isParent || isSmellItem + ? vscode.TreeItemCollapsibleState.Collapsed + : vscode.TreeItemCollapsibleState.None, + ); + + // Configure item based on type + if (isEnergySaved) { + this.iconPath = new vscode.ThemeIcon( + 'lightbulb', + new vscode.ThemeColor('charts.yellow'), + ); + this.tooltip = 'Estimated energy savings from this refactoring'; + } else if (isSmellItem) { + this.iconPath = new vscode.ThemeIcon( + 'warning', + new vscode.ThemeColor('charts.orange'), + ); + } else if (itemType === 'info') { + this.iconPath = new vscode.ThemeIcon('info'); + this.tooltip = this.description; + } + + // Make files clickable to open diffs + if (originalFilePath && refactoredFilePath && itemType !== 'info') { + this.command = { + command: 'ecooptimizer.openDiffEditor', + title: 'Compare Changes', + arguments: [originalFilePath, refactoredFilePath], + }; + this.contextValue = 'refactoringFile'; + } + } +} diff --git a/src/providers/SmellsViewProvider.ts b/src/providers/SmellsViewProvider.ts new file mode 100644 index 0000000..5694c8a --- /dev/null +++ b/src/providers/SmellsViewProvider.ts @@ -0,0 +1,304 @@ +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import * as path from 'path'; +import { buildPythonTree } from '../utils/TreeStructureBuilder'; +import { getAcronymByMessageId } from '../utils/smellsData'; +import { normalizePath } from '../utils/normalizePath'; +import { envConfig } from '../utils/envConfig'; + +/** + * Provides a tree view for displaying code smells in the workspace. + * Shows files and their detected smells in a hierarchical structure, + * with status indicators and navigation capabilities. + */ +export class SmellsViewProvider + implements vscode.TreeDataProvider +{ + // Event emitter for tree view updates + private _onDidChangeTreeData: vscode.EventEmitter< + TreeItem | SmellTreeItem | undefined | void + > = new vscode.EventEmitter(); + readonly onDidChangeTreeData: vscode.Event< + TreeItem | SmellTreeItem | undefined | void + > = this._onDidChangeTreeData.event; + + // Tracks analysis status and smells for each file + private fileStatuses: Map = new Map(); + private fileSmells: Map = new Map(); + + constructor(private context: vscode.ExtensionContext) {} + + /** + * Triggers a refresh of the tree view + */ + refresh(): void { + this._onDidChangeTreeData.fire(); + } + + /** + * Updates the analysis status for a file + * @param filePath Path to the file + * @param status New status ('queued', 'passed', 'failed', etc.) + */ + setStatus(filePath: string, status: string): void { + const normalizedPath = normalizePath(filePath); + this.fileStatuses.set(normalizedPath, status); + + // Clear smells if status is outdated + if (status === 'outdated') { + this.fileSmells.delete(normalizedPath); + } + + this._onDidChangeTreeData.fire(); + } + + /** + * Sets the detected smells for a file + * @param filePath Path to the file + * @param smells Array of detected smells + */ + setSmells(filePath: string, smells: Smell[]): void { + this.fileSmells.set(filePath, smells); + this._onDidChangeTreeData.fire(); + } + + /** + * Removes a file from the tree view + * @param filePath Path to the file to remove + * @returns Whether the file was found and removed + */ + public removeFile(filePath: string): boolean { + const normalizedPath = normalizePath(filePath); + const exists = this.fileStatuses.has(normalizedPath); + if (exists) { + this.fileStatuses.delete(normalizedPath); + this.fileSmells.delete(normalizedPath); + } + return exists; + } + + /** + * Clears all file statuses and smells from the view + */ + public clearAllStatuses(): void { + this.fileStatuses.clear(); + this.fileSmells.clear(); + this._onDidChangeTreeData.fire(); + } + + getTreeItem(element: TreeItem | SmellTreeItem): vscode.TreeItem { + return element; + } + + /** + * Builds the tree view hierarchy + * @param element The parent element or undefined for root items + * @returns Promise resolving to child tree items + */ + async getChildren( + element?: TreeItem | SmellTreeItem, + ): Promise<(TreeItem | SmellTreeItem)[]> { + const rootPath = this.context.workspaceState.get( + envConfig.WORKSPACE_CONFIGURED_PATH!, + ); + if (!rootPath) { + return []; + } + + // Smell nodes are leaf nodes - no children + if (element instanceof SmellTreeItem) { + return []; + } + + // If this is a file node, show its smells + if ( + element?.contextValue === 'file' || + element?.contextValue === 'file_with_smells' + ) { + const smells = this.fileSmells.get(element.fullPath) ?? []; + return smells.map((smell) => new SmellTreeItem(smell)); + } + + // Root element - show either single file or folder contents + if (!element) { + const stat = fs.statSync(rootPath); + if (stat.isFile()) { + return [this.createTreeItem(rootPath, true)]; + } else if (stat.isDirectory()) { + return [this.createTreeItem(rootPath, false)]; // Show root folder as top node + } + } + + // Folder node - build its contents + const currentPath = element?.resourceUri?.fsPath; + if (!currentPath) return []; + + const childNodes = buildPythonTree(currentPath); + return childNodes.map(({ fullPath, isFile }) => + this.createTreeItem(fullPath, isFile), + ); + } + + /** + * Creates a tree item for a file or folder + * @param filePath Path to the file/folder + * @param isFile Whether this is a file (false for folders) + * @returns Configured TreeItem instance + */ + private createTreeItem(filePath: string, isFile: boolean): TreeItem { + const label = path.basename(filePath); + const status = + this.fileStatuses.get(normalizePath(filePath)) ?? 'not_yet_detected'; + const icon = isFile ? getStatusIcon(status) : new vscode.ThemeIcon('folder'); + const tooltip = isFile ? getStatusMessage(status) : undefined; + + // Determine collapsible state: + // - Folders are always collapsible + // - Files are collapsible only if they have smells + const collapsibleState = isFile + ? this.fileSmells.has(filePath) && this.fileSmells.get(filePath)!.length > 0 + ? vscode.TreeItemCollapsibleState.Collapsed + : vscode.TreeItemCollapsibleState.None + : vscode.TreeItemCollapsibleState.Collapsed; + + const baseContext = isFile ? 'file' : 'directory'; + const item = new TreeItem(label, filePath, collapsibleState, baseContext); + item.iconPath = icon; + item.tooltip = tooltip; + + // Mark files with smells with special context + if ( + isFile && + this.fileSmells.has(filePath) && + this.fileSmells.get(filePath)!.length > 0 + ) { + item.contextValue = 'file_with_smells'; + } + + // Show outdated status in description + if (status === 'outdated') { + item.description = 'outdated'; + } + + return item; + } +} + +/** + * Tree item representing a file or folder in the smells view + */ +export class TreeItem extends vscode.TreeItem { + constructor( + label: string, + public readonly fullPath: string, + collapsibleState: vscode.TreeItemCollapsibleState, + contextValue: string, + ) { + super(label, collapsibleState); + this.resourceUri = vscode.Uri.file(fullPath); + this.contextValue = contextValue; + + // Make files clickable to open them + if (contextValue === 'file' || contextValue === 'file_with_smells') { + this.command = { + title: 'Open File', + command: 'vscode.open', + arguments: [this.resourceUri], + }; + } + } +} + +/** + * Tree item representing a detected code smell + */ +export class SmellTreeItem extends vscode.TreeItem { + constructor(public readonly smell: Smell) { + // Format label with acronym and line numbers + const acronym = getAcronymByMessageId(smell.messageId) ?? smell.messageId; + const lines = smell.occurences + ?.map((occ) => occ.line) + .filter((line) => line !== undefined) + .sort((a, b) => a - b) + .join(', '); + + const label = lines ? `${acronym}: Line ${lines}` : acronym; + super(label, vscode.TreeItemCollapsibleState.None); + + this.tooltip = smell.message; + this.contextValue = 'smell'; + this.iconPath = new vscode.ThemeIcon('snake'); + + // Set up command to jump to the first occurrence + const firstLine = smell.occurences?.[0]?.line; + if (smell.path && typeof firstLine === 'number') { + this.command = { + title: 'Jump to Smell', + command: 'ecooptimizer.jumpToSmell', + arguments: [smell.path, firstLine - 1], + }; + } + } +} + +/** + * Gets the appropriate icon for a file's analysis status + * @param status Analysis status string + * @returns ThemeIcon with appropriate icon and color + */ +export function getStatusIcon(status: string): vscode.ThemeIcon { + switch (status) { + case 'queued': + return new vscode.ThemeIcon( + 'sync~spin', + new vscode.ThemeColor('charts.yellow'), + ); + case 'passed': + return new vscode.ThemeIcon('pass', new vscode.ThemeColor('charts.green')); + case 'no_issues': + return new vscode.ThemeIcon('pass', new vscode.ThemeColor('charts.blue')); + case 'failed': + return new vscode.ThemeIcon('error', new vscode.ThemeColor('charts.red')); + case 'outdated': + return new vscode.ThemeIcon('warning', new vscode.ThemeColor('charts.orange')); + case 'server_down': + return new vscode.ThemeIcon( + 'server-process', + new vscode.ThemeColor('charts.red'), + ); + case 'refactoring': + return new vscode.ThemeIcon('robot', new vscode.ThemeColor('charts.purple')); + case 'accept-refactoring': + return new vscode.ThemeIcon('warning', new vscode.ThemeColor('charts.yellow')); + default: + return new vscode.ThemeIcon('circle-outline'); + } +} + +/** + * Gets a human-readable message for an analysis status + * @param status Analysis status string + * @returns Descriptive status message + */ +export function getStatusMessage(status: string): string { + switch (status) { + case 'queued': + return 'Analyzing Smells'; + case 'passed': + return 'Smells Successfully Detected'; + case 'failed': + return 'Error Detecting Smells'; + case 'no_issues': + return 'No Smells Found'; + case 'outdated': + return 'File Outdated - Needs Reanalysis'; + case 'server_down': + return 'Server Unavailable'; + case 'refactoring': + return 'Refactoring Currently Ongoing'; + case 'accept-refactoring': + return 'Successfully Refactored - Needs Reanalysis'; + default: + return 'Smells Not Yet Detected'; + } +} diff --git a/src/test/extension.test.ts b/src/test/extension.test.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/types.ts b/src/types.ts deleted file mode 100644 index 8785364..0000000 --- a/src/types.ts +++ /dev/null @@ -1,23 +0,0 @@ -interface Occurrence { - line: number; - column: number; - call_string: string; -} - -export interface Smell { - type: string; // Type of the smell (e.g., "performance", "convention") - symbol: string; // Symbolic identifier for the smell (e.g., "cached-repeated-calls") - message: string; // Detailed description of the smell - messageId: string; // Unique ID for the smell - line?: number; // Optional: Line number where the smell is detected - column?: number; // Optional: Column offset where the smell starts - endLine?: number; // Optional: Ending line for multiline smells - endColumn?: number; // Optional: Ending column for multiline smells - confidence: string; // Confidence level (e.g., "HIGH", "MEDIUM") - path?: string; // Optional: Relative file path - absolutePath?: string; // Optional: Absolute file pat. - module?: string; // Optional: Module name - obj?: string; // Optional: Object name associated with the smell (if applicable) - repetitions?: number; // Optional: Number of repeated occurrences - occurrences?: Occurrence[]; // Optional: List of occurrences for repeated calls -} diff --git a/src/ui/fileHighlighter.ts b/src/ui/fileHighlighter.ts index f8afd5b..e91d62b 100644 --- a/src/ui/fileHighlighter.ts +++ b/src/ui/fileHighlighter.ts @@ -1,33 +1,217 @@ -import * as vscode from "vscode"; +import * as vscode from 'vscode'; +import { SmellsCacheManager } from '../context/SmellsCacheManager'; +import { ConfigManager } from '../context/configManager'; +import { getEnabledSmells } from '../utils/smellsData'; +/** + * The `FileHighlighter` class is responsible for managing and applying visual highlights + * to code smells in the VS Code editor. It uses cached smell data to determine which + * lines to highlight and applies decorations to the editor accordingly. + */ export class FileHighlighter { - static highlightSmells(editor: vscode.TextEditor, smells: any[]) { - + private static instance: FileHighlighter | undefined; + private decorations: vscode.TextEditorDecorationType[] = []; - const yellowUnderline = vscode.window.createTextEditorDecorationType({ - textDecoration: 'underline yellow', - }); - + private constructor(private smellsCacheManager: SmellsCacheManager) { + this.smellsCacheManager.onSmellsUpdated((target) => { + if (target === 'all') { + this.updateHighlightsForVisibleEditors(); + } else { + this.updateHighlightsForFile(target); + } + }); + } - const decorations: vscode.DecorationOptions[] = smells.filter((smell: Smell) => isValidLine(smell.line)).map((smell: any) => { - const line = smell.line - 1; // convert to zero-based line index for VS editor - const range = new vscode.Range(line, 0, line, editor.document.lineAt(line).text.length); + /** + * Retrieves the singleton instance of the `FileHighlighter` class. + * If the instance does not exist, it is created. + * + * @param smellsCacheManager - The manager responsible for caching and providing smell data. + * @returns The singleton instance of `FileHighlighter`. + */ + public static getInstance( + smellsCacheManager: SmellsCacheManager, + ): FileHighlighter { + if (!FileHighlighter.instance) { + FileHighlighter.instance = new FileHighlighter(smellsCacheManager); + } + return FileHighlighter.instance; + } - return { range, hoverMessage: `Smell: ${smell.message}` }; // option to hover over and read smell details - }); + /** + * Updates highlights for a specific file if it is currently open in a visible editor. + * + * @param filePath - The file path of the target file to update highlights for. + */ + private updateHighlightsForFile(filePath: string): void { + if (!filePath.endsWith('.py')) { + return; + } - editor.setDecorations(yellowUnderline, decorations); - console.log("Updated smell line highlights"); + const editor = vscode.window.visibleTextEditors.find( + (e) => e.document.uri.fsPath === filePath, + ); + if (editor) { + this.highlightSmells(editor); } -} + } + + /** + * Updates highlights for all currently visible editors. + */ + public updateHighlightsForVisibleEditors(): void { + vscode.window.visibleTextEditors.forEach((editor) => { + if (!editor.document.fileName.endsWith('.py')) { + return; + } + this.highlightSmells(editor); + }); + } + + /** + * Resets all active highlights by disposing of all decorations. + */ + public resetHighlights(): void { + this.decorations.forEach((decoration) => decoration.dispose()); + this.decorations = []; + } -function isValidLine(line: any): boolean { - return ( - line !== undefined && - line !== null && - typeof line === 'number' && - Number.isFinite(line) && - line > 0 && - Number.isInteger(line) + /** + * Highlights code smells in the given editor based on cached smell data. + * Resets existing highlights before applying new ones. + * + * @param editor - The text editor to apply highlights to. + */ + public highlightSmells(editor: vscode.TextEditor): void { + this.resetHighlights(); + + const smells = this.smellsCacheManager.getCachedSmells( + editor.document.uri.fsPath, + ); + + if (!smells) { + return; + } + + const smellColours = ConfigManager.get<{ + [key: string]: string; + }>('smellsColours', {}); + + const useSingleColour = ConfigManager.get('useSingleColour', false); + const singleHighlightColour = ConfigManager.get( + 'singleHighlightColour', + 'rgba(255, 204, 0, 0.5)', ); + const highlightStyle = ConfigManager.get('highlightStyle', 'underline'); + + const activeSmells = new Set(smells.map((smell) => smell.symbol)); + + const enabledSmells = getEnabledSmells(); + + activeSmells.forEach((smellType) => { + const smellColour = smellColours[smellType]; + + if (enabledSmells[smellType]) { + const colour = useSingleColour ? singleHighlightColour : smellColour; + + this.highlightSmell(editor, smells, smellType, colour, highlightStyle); + } + }); + } + + /** + * Highlights a specific type of smell in the given editor. + * Filters smell occurrences to ensure they are valid and match the target smell type. + * + * @param editor - The text editor to apply highlights to. + * @param smells - The list of all smells for the file. + * @param targetSmell - The specific smell type to highlight. + * @param colour - The colour to use for the highlight. + * @param style - The style of the highlight (e.g., underline, flashlight, border-arrow). + */ + private highlightSmell( + editor: vscode.TextEditor, + smells: Smell[], + targetSmell: string, + colour: string, + style: string, + ): void { + const smellLines: vscode.DecorationOptions[] = smells + .filter((smell: Smell) => { + const valid = smell.occurences.every((occurrence: { line: number }) => + isValidLine(occurrence.line, editor.document.lineCount), + ); + const isCorrectType = smell.symbol === targetSmell; + + return valid && isCorrectType; + }) + .map((smell: Smell) => { + const line = smell.occurences[0].line - 1; // convert to zero-based line index for VS editor + const lineText = editor.document.lineAt(line).text; + const indexStart = lineText.length - lineText.trimStart().length; + const indexEnd = lineText.trimEnd().length + 2; + const range = new vscode.Range(line, indexStart, line, indexEnd); + return { range }; + }); + + const decoration = this.getDecoration(colour, style); + + editor.setDecorations(decoration, smellLines); + this.decorations.push(decoration); + } + + /** + * Creates a text editor decoration type based on the given colour and style. + * + * @param colour - The colour to use for the decoration. + * @param style - The style of the decoration (e.g., underline, flashlight, border-arrow). + * @returns A `vscode.TextEditorDecorationType` object representing the decoration. + */ + private getDecoration( + colour: string, + style: string, + ): vscode.TextEditorDecorationType { + switch (style) { + case 'underline': + return vscode.window.createTextEditorDecorationType({ + textDecoration: `wavy ${colour} underline 1px`, + }); + case 'flashlight': + return vscode.window.createTextEditorDecorationType({ + isWholeLine: true, + backgroundColor: colour, + }); + case 'border-arrow': + return vscode.window.createTextEditorDecorationType({ + borderWidth: '1px 2px 1px 0', + borderStyle: 'solid', + borderColor: colour, + after: { + contentText: '▶', + margin: '0 0 0 5px', + color: colour, + fontWeight: 'bold', + }, + overviewRulerColor: colour, + overviewRulerLane: vscode.OverviewRulerLane.Right, + }); + default: + return vscode.window.createTextEditorDecorationType({ + textDecoration: `wavy ${colour} underline 1px`, + }); + } + } +} + +function isValidLine(line: any, lineCount: number): boolean { + const isValid = + line !== undefined && + line !== null && + typeof line === 'number' && + Number.isFinite(line) && + line > 0 && + Number.isInteger(line) && + line <= lineCount; + + return isValid; } diff --git a/src/ui/hoverManager.ts b/src/ui/hoverManager.ts index e69de29..7e8e0af 100644 --- a/src/ui/hoverManager.ts +++ b/src/ui/hoverManager.ts @@ -0,0 +1,89 @@ +import * as vscode from 'vscode'; +import { SmellsCacheManager } from '../context/SmellsCacheManager'; + +/** + * Provides hover information for detected code smells in Python files. + * Shows smell details and quick actions when hovering over affected lines. + */ +export class HoverManager implements vscode.HoverProvider { + constructor(private smellsCacheManager: SmellsCacheManager) {} + + /** + * Registers the hover provider with VS Code + * @param context The extension context for managing disposables + */ + public register(context: vscode.ExtensionContext): void { + const selector: vscode.DocumentSelector = { + language: 'python', + scheme: 'file', // Only show for local files, not untitled documents + }; + const disposable = vscode.languages.registerHoverProvider(selector, this); + context.subscriptions.push(disposable); + } + + /** + * Generates hover content when hovering over lines with detected smells + * @param document The active text document + * @param position The cursor position where hover was triggered + * @returns Hover content or undefined if no smells found + */ + public provideHover( + document: vscode.TextDocument, + position: vscode.Position, + _token: vscode.CancellationToken, + ): vscode.ProviderResult { + const filePath = document.uri.fsPath; + + if (!filePath.endsWith('.py')) return; + + const smells = this.smellsCacheManager.getCachedSmells(filePath); + if (!smells || smells.length === 0) return; + + // Convert VS Code position to 1-based line number + const lineNumber = position.line + 1; + + // Find smells that occur on this line + const smellsAtLine = smells.filter((smell) => + smell.occurences.some((occ) => occ.line === lineNumber), + ); + + if (smellsAtLine.length === 0) return; + + // Helper to escape markdown special characters + const escape = (text: string): string => { + return text.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&'); + }; + + // Build markdown content with smell info and actions + const markdown = new vscode.MarkdownString(); + markdown.isTrusted = true; // Allow command URIs + markdown.supportHtml = true; + markdown.supportThemeIcons = true; + + // Add each smell's info and actions + smellsAtLine.forEach((smell) => { + // Basic smell info + const messageLine = `${escape(smell.message)} (**${escape(smell.messageId)}**)`; + const divider = '\n\n---\n\n'; // Visual separator + + // Command URIs for quick actions + const refactorSmellCmd = `command:ecooptimizer.refactorSmell?${encodeURIComponent(JSON.stringify(smell))} "Fix this specific smell"`; + const refactorTypeCmd = `command:ecooptimizer.refactorAllSmellsOfType?${encodeURIComponent( + JSON.stringify({ + fullPath: filePath, + smellType: smell.messageId, + }), + )} "Fix all similar smells"`; + + // Build the hover content + markdown.appendMarkdown(messageLine); + markdown.appendMarkdown(divider); + markdown.appendMarkdown(`[$(tools) Refactor Smell](${refactorSmellCmd}) | `); + markdown.appendMarkdown( + `[$(tools) Refactor All of This Type](${refactorTypeCmd})`, + ); + }); + + return new vscode.Hover(markdown); + } +} diff --git a/src/ui/lineSelectionManager.ts b/src/ui/lineSelectionManager.ts new file mode 100644 index 0000000..5b1c1e2 --- /dev/null +++ b/src/ui/lineSelectionManager.ts @@ -0,0 +1,93 @@ +import * as vscode from 'vscode'; +import { SmellsCacheManager } from '../context/SmellsCacheManager'; + +/** + * Manages line selection and decoration in a VS Code editor, specifically for + * displaying comments related to code smells. + */ +export class LineSelectionManager { + private decoration: vscode.TextEditorDecorationType | null = null; + private lastDecoratedLine: number | null = null; + + constructor(private smellsCacheManager: SmellsCacheManager) { + // Listen for smell cache being cleared for any file + this.smellsCacheManager.onSmellsUpdated((targetFilePath) => { + if (targetFilePath === 'all') { + this.removeLastComment(); + return; + } + + const activeEditor = vscode.window.activeTextEditor; + if (activeEditor && activeEditor.document.uri.fsPath === targetFilePath) { + this.removeLastComment(); + } + }); + } + + /** + * Removes the last applied decoration from the editor, if any. + */ + public removeLastComment(): void { + if (this.decoration) { + this.decoration.dispose(); + this.decoration = null; + } + this.lastDecoratedLine = null; + } + + /** + * Adds a comment to the currently selected line in the editor. + */ + public commentLine(editor: vscode.TextEditor): void { + if (!editor) return; + + const filePath = editor.document.fileName; + const smells = this.smellsCacheManager.getCachedSmells(filePath); + if (!smells) { + this.removeLastComment(); // If cache is gone, clear any previous comment + return; + } + + const { selection } = editor; + if (!selection.isSingleLine) return; + + const selectedLine = selection.start.line; + + if (this.lastDecoratedLine === selectedLine) return; + + this.removeLastComment(); + this.lastDecoratedLine = selectedLine; + + const smellsAtLine = smells.filter((smell) => + smell.occurences.some((occ) => occ.line === selectedLine + 1), + ); + + if (smellsAtLine.length === 0) return; + + let comment = `🍂 Smell: ${smellsAtLine[0].symbol}`; + if (smellsAtLine.length > 1) { + comment += ` | (+${smellsAtLine.length - 1})`; + } + + const themeColor = new vscode.ThemeColor('editorLineNumber.foreground'); + this.decoration = vscode.window.createTextEditorDecorationType({ + isWholeLine: true, + after: { + contentText: comment, + color: themeColor, + margin: '0 0 0 10px', + textDecoration: 'none', + }, + }); + + const lineText = editor.document.lineAt(selectedLine).text; + const range = new vscode.Range( + selectedLine, + 0, + selectedLine, + lineText.trimEnd().length + 1, + ); + + editor.setDecorations(this.decoration, [range]); + } +} diff --git a/src/ui/refactorManager.ts b/src/ui/refactorManager.ts deleted file mode 100644 index 67676b9..0000000 --- a/src/ui/refactorManager.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as vscode from "vscode"; - - -export class RefactorManager { - static async previewRefactor(editor: vscode.TextEditor, refactoredCode: string) { - try { - // Create a new untitled document for preview - const previewDocument = await vscode.workspace.openTextDocument({ - content: refactoredCode, - language: "python", // Adjust this to the language of your file - }); - - // Show the document in a new editor column - await vscode.window.showTextDocument(previewDocument, vscode.ViewColumn.Beside); - } catch (error) { - vscode.window.showErrorMessage(`Eco: Error showing refactor preview: ${error}`); - } - } -} diff --git a/src/utils/TreeStructureBuilder.ts b/src/utils/TreeStructureBuilder.ts new file mode 100644 index 0000000..8ad1a4a --- /dev/null +++ b/src/utils/TreeStructureBuilder.ts @@ -0,0 +1,117 @@ +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import * as path from 'path'; +import { normalizePath } from './normalizePath'; + +/** + * Options for configuring tree node appearance and behavior in the VS Code UI + */ +export interface TreeNodeOptions { + /** Determines context menu commands and visibility rules */ + contextValue: string; + /** Optional icon from VS Code's icon set */ + icon?: vscode.ThemeIcon; + /** Tooltip text shown on hover */ + tooltip?: string; + /** Command to execute when node is clicked */ + command?: vscode.Command; +} + +/** + * Represents a node in the file system tree structure + */ +export interface TreeNode { + /** Display name in the tree view */ + label: string; + /** Absolute filesystem path */ + fullPath: string; + /** Whether this represents a file (true) or directory (false) */ + isFile: boolean; + /** Additional UI/behavior configuration */ + options?: TreeNodeOptions; +} + +/** + * Builds a hierarchical tree structure of Python files and directories containing Python files + * @param rootPath - The absolute path to start building the tree from + * @returns Array of TreeNode objects representing the directory structure + */ +export function buildPythonTree(rootPath: string): TreeNode[] { + const nodes: TreeNode[] = []; + + try { + const entries = fs.readdirSync(rootPath); + + for (const entry of entries) { + const fullPath = normalizePath(path.join(rootPath, entry)); + const stat = fs.statSync(fullPath); + + if (stat.isDirectory()) { + // Only include directories that contain Python files + if (containsPythonFiles(fullPath)) { + nodes.push({ + label: entry, + fullPath, + isFile: false, + options: { + contextValue: 'folder', + icon: vscode.ThemeIcon.Folder, + }, + }); + } + } else if (stat.isFile() && entry.endsWith('.py')) { + nodes.push({ + label: entry, + fullPath, + isFile: true, + options: { + contextValue: 'file', + icon: vscode.ThemeIcon.File, + }, + }); + } + } + } catch (err) { + vscode.window.showErrorMessage(`Failed to read directory: ${rootPath}`); + console.error(`Directory read error: ${err}`); + } + + return nodes.sort((a, b) => { + // Directories first, then alphabetical + if (!a.isFile && b.isFile) return -1; + if (a.isFile && !b.isFile) return 1; + return a.label.localeCompare(b.label); + }); +} + +/** + * Recursively checks if a directory contains any Python files + * @param folderPath - Absolute path to the directory to check + * @returns True if any .py files exist in this directory or subdirectories + */ +function containsPythonFiles(folderPath: string): boolean { + try { + const entries = fs.readdirSync(folderPath); + + for (const entry of entries) { + const fullPath = normalizePath(path.join(folderPath, entry)); + const stat = fs.statSync(fullPath); + + if (stat.isFile() && entry.endsWith('.py')) { + return true; + } + + if (stat.isDirectory()) { + // Short-circuit if any subdirectory contains Python files + if (containsPythonFiles(fullPath)) { + return true; + } + } + } + } catch (err) { + vscode.window.showErrorMessage(`Failed to scan directory: ${folderPath}`); + console.error(`Directory scan error: ${err}`); + } + + return false; +} diff --git a/src/utils/editorUtils.ts b/src/utils/editorUtils.ts deleted file mode 100644 index ab6c92f..0000000 --- a/src/utils/editorUtils.ts +++ /dev/null @@ -1,20 +0,0 @@ -import * as vscode from 'vscode'; - -/** - * Gets the active editor and its file path if an editor is open. - * @returns {{ editor: vscode.TextEditor | undefined, filePath: string | undefined }} - * An object containing the active editor and the file path, or undefined for both if no editor is open. - */ -export function getEditorAndFilePath(): {editor: vscode.TextEditor | undefined, filePath: string | undefined}{ - const activeEditor = vscode.window.activeTextEditor; - const filePath = activeEditor?.document.uri.fsPath; - return { editor: activeEditor, filePath }; -} - -/** - * Gets the active editor if an editor is open. - */ -export function getEditor(): vscode.TextEditor | undefined { - return vscode.window.activeTextEditor; -} - diff --git a/src/utils/envConfig.ts b/src/utils/envConfig.ts new file mode 100644 index 0000000..45f3bd0 --- /dev/null +++ b/src/utils/envConfig.ts @@ -0,0 +1,17 @@ +export interface EnvConfig { + SERVER_URL?: string; + SMELL_CACHE_KEY?: string; + HASH_PATH_MAP_KEY?: string; + WORKSPACE_METRICS_DATA?: string; + WORKSPACE_CONFIGURED_PATH?: string; + UNFINISHED_REFACTORING?: string; +} + +export const envConfig: EnvConfig = { + SERVER_URL: '127.0.0.1:8000', + SMELL_CACHE_KEY: 'smellCacheKey', + HASH_PATH_MAP_KEY: 'hashMapKey', + WORKSPACE_METRICS_DATA: 'workspaceMetrics', + WORKSPACE_CONFIGURED_PATH: 'workspacePath', + UNFINISHED_REFACTORING: 'pastRefactor', +}; diff --git a/src/utils/initializeStatusesFromCache.ts b/src/utils/initializeStatusesFromCache.ts new file mode 100644 index 0000000..27beaeb --- /dev/null +++ b/src/utils/initializeStatusesFromCache.ts @@ -0,0 +1,108 @@ +import * as vscode from 'vscode'; +import * as fs from 'fs/promises'; +import { SmellsCacheManager } from '../context/SmellsCacheManager'; +import { SmellsViewProvider } from '../providers/SmellsViewProvider'; +import { ecoOutput } from '../extension'; +import { normalizePath } from './normalizePath'; +import { envConfig } from './envConfig'; + +/** + * Initializes file statuses and smells in the SmellsViewProvider from the smell cache. + + * @param context The extension context containing workspace configuration + * @param smellsCacheManager The cache manager instance + * @param smellsViewProvider The view provider to update with cached data + */ +export async function initializeStatusesFromCache( + context: vscode.ExtensionContext, + smellsCacheManager: SmellsCacheManager, + smellsViewProvider: SmellsViewProvider, +): Promise { + ecoOutput.info('workspace key: ', envConfig.WORKSPACE_CONFIGURED_PATH); + + // Get configured workspace path from extension state + let configuredPath = context.workspaceState.get( + envConfig.WORKSPACE_CONFIGURED_PATH!, + ); + + if (!configuredPath) { + ecoOutput.warn( + '[CacheInit] No configured workspace path found - skipping cache initialization', + ); + return; + } + + configuredPath = normalizePath(configuredPath); + ecoOutput.info( + `[CacheInit] Starting cache initialization for workspace: ${configuredPath}`, + ); + + // Get all cached file paths and initialize counters + const pathMap = smellsCacheManager.getAllFilePaths(); + ecoOutput.trace(`[CacheInit] Found ${pathMap.length} files in cache`); + ecoOutput.trace(`[CacheInit] Found ${pathMap} files in cache`); + + let validFiles = 0; + let removedFiles = 0; + let filesWithSmells = 0; + let cleanFiles = 0; + + // Process each cached file + for (const filePath of pathMap) { + ecoOutput.trace(`[CacheInit] Processing cache entry: ${filePath}`); + + // Skip files outside the current workspace + if (!filePath.startsWith(configuredPath)) { + ecoOutput.trace( + `[CacheInit] File outside workspace - removing from cache: ${filePath}`, + ); + await smellsCacheManager.clearCachedSmellsForFile(filePath); + removedFiles++; + continue; + } + + // Verify file exists on disk + try { + await fs.access(filePath); + ecoOutput.trace(`[CacheInit] File verified: ${filePath}`); + } catch { + ecoOutput.trace( + `[CacheInit] File not found - removing from cache: ${filePath}`, + ); + await smellsCacheManager.clearCachedSmellsForFile(filePath); + removedFiles++; + continue; + } + + // Get cached smells for valid files + const smells = smellsCacheManager.getCachedSmells(filePath); + if (smells !== undefined) { + validFiles++; + + // Update view provider based on smell data + if (smells.length > 0) { + ecoOutput.trace( + `[CacheInit] Found ${smells.length} smells for file: ${filePath}`, + ); + smellsViewProvider.setStatus(filePath, 'passed'); + smellsViewProvider.setSmells(filePath, smells); + filesWithSmells++; + } else { + ecoOutput.trace(`[CacheInit] File has no smells: ${filePath}`); + smellsViewProvider.setStatus(filePath, 'no_issues'); + cleanFiles++; + } + } else { + ecoOutput.trace( + `[CacheInit] No cache data found for file (should not happen): ${filePath}`, + ); + } + } + + // Log summary statistics + ecoOutput.info( + `[CacheInit] Cache initialization complete. ` + + `Results: ${validFiles} valid files (${filesWithSmells} with smells, ${cleanFiles} clean), ` + + `${removedFiles} files removed from cache`, + ); +} diff --git a/src/utils/normalizePath.ts b/src/utils/normalizePath.ts new file mode 100644 index 0000000..7071fcb --- /dev/null +++ b/src/utils/normalizePath.ts @@ -0,0 +1,8 @@ +/** + * Normalizes file paths for consistent comparison and caching + * @param filePath - The file path to normalize + * @returns Lowercase version of the path for case-insensitive operations + */ +export function normalizePath(filePath: string): string { + return filePath.toLowerCase(); +} diff --git a/src/utils/refactorActionButtons.ts b/src/utils/refactorActionButtons.ts new file mode 100644 index 0000000..f4c9a93 --- /dev/null +++ b/src/utils/refactorActionButtons.ts @@ -0,0 +1,71 @@ +import * as vscode from 'vscode'; +import { ecoOutput } from '../extension'; + +let acceptButton: vscode.StatusBarItem | undefined; +let rejectButton: vscode.StatusBarItem | undefined; + +/** + * Create and register the status bar buttons (called once at activation). + */ +export function initializeRefactorActionButtons( + context: vscode.ExtensionContext, +): void { + ecoOutput.trace('Initializing refactor action buttons...'); + + acceptButton = vscode.window.createStatusBarItem( + vscode.StatusBarAlignment.Right, + 0, + ); + rejectButton = vscode.window.createStatusBarItem( + vscode.StatusBarAlignment.Right, + 1, + ); + + acceptButton.text = '$(check) ACCEPT REFACTOR'; + acceptButton.command = 'ecooptimizer.acceptRefactoring'; + acceptButton.tooltip = 'Accept and apply the suggested refactoring'; + acceptButton.color = new vscode.ThemeColor('charts.green'); + + rejectButton.text = '$(x) REJECT REFACTOR'; + rejectButton.command = 'ecooptimizer.rejectRefactoring'; + rejectButton.tooltip = 'Reject the suggested refactoring'; + rejectButton.color = new vscode.ThemeColor('charts.red'); + + context.subscriptions.push(acceptButton, rejectButton); + + ecoOutput.trace('Status bar buttons created and registered.'); +} + +/** + * Show the status bar buttons when a refactoring is in progress. + */ +export function showRefactorActionButtons(): void { + if (!acceptButton || !rejectButton) { + ecoOutput.trace( + '❌ Tried to show refactor buttons but they are not initialized.', + ); + return; + } + + ecoOutput.trace('Showing refactor action buttons...'); + acceptButton.show(); + rejectButton.show(); + vscode.commands.executeCommand('setContext', 'refactoringInProgress', true); +} + +/** + * Hide the status bar buttons when the refactoring ends. + */ +export function hideRefactorActionButtons(): void { + if (!acceptButton || !rejectButton) { + ecoOutput.trace( + '❌ Tried to hide refactor buttons but they are not initialized.', + ); + return; + } + + ecoOutput.replace('Hiding refactor action buttons...'); + acceptButton.hide(); + rejectButton.hide(); + vscode.commands.executeCommand('setContext', 'refactoringInProgress', false); +} diff --git a/src/utils/smellsData.ts b/src/utils/smellsData.ts new file mode 100644 index 0000000..655fbe2 --- /dev/null +++ b/src/utils/smellsData.ts @@ -0,0 +1,166 @@ +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import * as path from 'path'; +import { ecoOutput } from '../extension'; + +/** + * Defines the structure of the smell configuration in smells.json. + * Used by FilterSmellsProvider.ts (modifies JSON based on user input). + */ +export interface FilterSmellConfig { + name: string; + message_id: string; + acronym: string; + smell_description: string; + enabled: boolean; + analyzer_options?: Record< + string, + { label: string; description: string; value: number | string } + >; +} + +/** + * Defines the structure of enabled smells sent to the backend. + */ +interface DetectSmellConfig { + message_id: string; + acronym: string; + options: Record; +} + +let filterSmells: Record; +let enabledSmells: Record; + +/** + * Loads the full smells configuration from smells.json. + * @param version - The version of the smells configuration to load. + * @returns A dictionary of smells with their respective configuration. + */ +export function loadSmells(version: 'working' | 'default' = 'working'): void { + const filePath = path.join( + __dirname, + '..', + 'data', + `${version}_smells_config.json`, + ); + + if (!fs.existsSync(filePath)) { + vscode.window.showErrorMessage( + 'Configuration file missing: smells.json could not be found.', + ); + } + + try { + filterSmells = JSON.parse(fs.readFileSync(filePath, 'utf-8')); + enabledSmells = parseSmells(filterSmells); + + ecoOutput.info(`[smellsData.ts] Loaded smells configuration: ${version}`); + } catch (error) { + vscode.window.showErrorMessage( + 'Error loading smells.json. Please check the file format.', + ); + console.error('ERROR: Failed to parse smells.json', error); + } +} + +/** + * Saves the smells configuration to smells.json. + * @param smells - The smells data to be saved. + */ +export function saveSmells(smells: Record): void { + filterSmells = smells; + + const filePath = path.join(__dirname, '..', 'data', 'working_smells_config.json'); + + enabledSmells = parseSmells(filterSmells); + try { + fs.writeFileSync(filePath, JSON.stringify(smells, null, 2)); + } catch (error) { + vscode.window.showErrorMessage('Error saving smells.json.'); + console.error('ERROR: Failed to write smells.json', error); + } +} + +/** + * Extracts raw smells data from the loaded configuration. + * @returns A dictionary of smell config data for smell filtering. + */ +export function getFilterSmells(): Record { + return filterSmells; +} + +/** + * Extracts enabled smells from the loaded configuration. + * @returns A dictionary of enabled smells formatted for backend processing. + */ +export function getEnabledSmells(): Record { + return enabledSmells; +} + +/** + * Parses the raw smells into a formatted object. + * @param smells - The smells data to be saved. + * @returns A dictionary of enabled smells formatted for backend processing. + */ +function parseSmells( + smells: Record, +): Record { + return Object.fromEntries( + Object.entries(smells) + .filter(([, smell]) => smell.enabled) + .map(([smellKey, smellData]) => [ + smellKey, + { + message_id: smellData.message_id, + acronym: smellData.acronym, + options: Object.fromEntries( + Object.entries(smellData.analyzer_options ?? {}).map( + ([optionKey, optionData]) => [ + optionKey, + typeof optionData.value === 'string' || + typeof optionData.value === 'number' + ? optionData.value + : String(optionData.value), + ], + ), + ), + }, + ]), + ); +} + +/** + * Returns the acronym for a given message ID. + * @param messageId - The message ID to look up (e.g., "R0913"). + * @returns The acronym (e.g., "LPL") or undefined if not found. + */ +export function getAcronymByMessageId(messageId: string): string | undefined { + const match = Object.values(filterSmells).find( + (smell) => smell.message_id === messageId, + ); + return match?.acronym; +} + +/** + * Returns the full name for a given message ID. + * @param messageId - The message ID to look up (e.g., "R0913"). + * @returns The full name (e.g., "Long Parameter List") or undefined if not found. + */ +export function getNameByMessageId(messageId: string): string | undefined { + const match = Object.values(filterSmells).find( + (smell) => smell.message_id === messageId, + ); + return match?.name; +} + +/** + * Returns the description for a given message ID. + * @param messageId - The message ID to look up (e.g., "R0913"). + * @returns The description or undefined if not found. + */ +export function getDescriptionByMessageId(messageId: string): string | undefined { + const match = Object.values(filterSmells).find( + (smell) => smell.message_id === messageId, + ); + return match?.smell_description; // This assumes your FilterSmellConfig has a description field +} diff --git a/src/utils/trackedDiffEditors.ts b/src/utils/trackedDiffEditors.ts new file mode 100644 index 0000000..be54e61 --- /dev/null +++ b/src/utils/trackedDiffEditors.ts @@ -0,0 +1,35 @@ +// utils/trackedDiffEditors.ts +import * as vscode from 'vscode'; + +export const trackedDiffs = new Set(); + +export function registerDiffEditor( + original: vscode.Uri, + modified: vscode.Uri, +): void { + trackedDiffs.add(`${original.toString()}::${modified.toString()}`); +} + +export function isTrackedDiffEditor( + original: vscode.Uri, + modified: vscode.Uri, +): boolean { + return trackedDiffs.has(`${original.toString()}::${modified.toString()}`); +} + +export async function closeAllTrackedDiffEditors(): Promise { + const tabs = vscode.window.tabGroups.all.flatMap((group) => group.tabs); + + for (const tab of tabs) { + if (tab.input && (tab.input as any).modified && (tab.input as any).original) { + const original = (tab.input as any).original as vscode.Uri; + const modified = (tab.input as any).modified as vscode.Uri; + + if (isTrackedDiffEditor(original, modified)) { + await vscode.window.tabGroups.close(tab, true); + } + } + } + + trackedDiffs.clear(); +} diff --git a/test/__mocks__/crypto.ts b/test/__mocks__/crypto.ts new file mode 100644 index 0000000..9bd59a3 --- /dev/null +++ b/test/__mocks__/crypto.ts @@ -0,0 +1,8 @@ +// __mocks__/crypto.ts +export default { + createHash: jest.fn(() => ({ + update: jest.fn(() => ({ + digest: jest.fn(() => 'mocked-hash'), + })), + })), +}; diff --git a/test/api/backend.test.ts b/test/api/backend.test.ts new file mode 100644 index 0000000..1dde1d0 --- /dev/null +++ b/test/api/backend.test.ts @@ -0,0 +1,278 @@ +/* eslint-disable unused-imports/no-unused-imports */ +import path from 'path'; + +import { envConfig } from '../../src/utils/envConfig'; +import { + checkServerStatus, + initLogs, + fetchSmells, + backendRefactorSmell, + backendRefactorSmellType, +} from '../../src/api/backend'; +import { serverStatus, ServerStatusType } from '../../src/emitters/serverStatus'; +import { ecoOutput } from '../../src/extension'; + +// Mock dependencies +jest.mock('../../src/emitters/serverStatus'); +jest.mock('../../src/extension'); +jest.mock('../../src/utils/envConfig'); +jest.mock('path', () => ({ + basename: jest.fn((path) => path.split('/').pop()), +})); + +// Mock global fetch +global.fetch = jest.fn() as jest.Mock; + +describe('Backend Service', () => { + const mockServerUrl = 'localhost:8000'; + const mockLogDir = '/path/to/logs'; + const mockFilePath = '/project/src/file.py'; + const mockWorkspacePath = '/project'; + const mockSmell = { + symbol: 'test-smell', + path: mockFilePath, + occurences: [{ line: 1 }], + message: 'Test smell message', + messageId: 'test-001', + } as unknown as Smell; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('checkServerStatus', () => { + it('should set status UP when server is healthy', async () => { + (fetch as jest.Mock).mockResolvedValueOnce({ ok: true }); + + await checkServerStatus(); + + expect(fetch).toHaveBeenCalledWith(`http://${mockServerUrl}/health`); + expect(serverStatus.setStatus).toHaveBeenCalledWith(ServerStatusType.UP); + expect(ecoOutput.trace).toHaveBeenCalledWith( + '[backend.ts] Backend server is healthy', + ); + }); + + it('should set status DOWN when server responds with error', async () => { + (fetch as jest.Mock).mockResolvedValueOnce({ ok: false, status: 500 }); + + await checkServerStatus(); + + expect(serverStatus.setStatus).toHaveBeenCalledWith(ServerStatusType.DOWN); + expect(ecoOutput.warn).toHaveBeenCalledWith( + '[backend.ts] Backend server unhealthy status: 500', + ); + }); + + it('should set status DOWN and log error when request fails', async () => { + const mockError = new Error('Network error'); + (fetch as jest.Mock).mockRejectedValueOnce(mockError); + + await checkServerStatus(); + + expect(serverStatus.setStatus).toHaveBeenCalledWith(ServerStatusType.DOWN); + expect(ecoOutput.error).toHaveBeenCalledWith( + '[backend.ts] Server connection failed: Network error', + ); + }); + }); + + describe('initLogs', () => { + it('should successfully initialize logs', async () => { + (fetch as jest.Mock).mockResolvedValueOnce({ ok: true }); + + const result = await initLogs(mockLogDir); + + expect(fetch).toHaveBeenCalledWith(`http://${mockServerUrl}/logs/init`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ log_dir: mockLogDir }), + }); + expect(result).toBe(true); + }); + + it('should return false when server responds with not ok', async () => { + (fetch as jest.Mock).mockResolvedValueOnce({ ok: false }); + + const result = await initLogs(mockLogDir); + + expect(result).toBe(false); + expect(ecoOutput.error).toHaveBeenCalledWith( + expect.stringContaining('Unable to initialize logging'), + ); + }); + + it('should handle network errors', async () => { + (fetch as jest.Mock).mockRejectedValueOnce(new Error('Network failed')); + + const result = await initLogs(mockLogDir); + + expect(result).toBe(false); + expect(ecoOutput.error).toHaveBeenCalledWith( + 'Eco: Unable to reach the backend. Please check your connection.', + ); + }); + }); + + describe('fetchSmells', () => { + const mockEnabledSmells = { 'test-smell': { threshold: 0.5 } }; + const mockSmellsResponse = [mockSmell]; + + it('should successfully fetch smells', async () => { + const mockResponse = { + ok: true, + status: 200, + statusText: 'OK', + headers: new Headers(), + json: jest.fn().mockResolvedValueOnce(mockSmellsResponse), + }; + (fetch as jest.Mock).mockResolvedValueOnce(mockResponse); + + const result = await fetchSmells(mockFilePath, mockEnabledSmells); + + expect(fetch).toHaveBeenCalledWith(`http://${mockServerUrl}/smells`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + file_path: mockFilePath, + enabled_smells: mockEnabledSmells, + }), + }); + expect(result).toEqual({ smells: mockSmellsResponse, status: 200 }); + expect(ecoOutput.info).toHaveBeenCalledWith( + '[backend.ts] Starting smell detection for: file.py', + ); + expect(ecoOutput.info).toHaveBeenCalledWith( + '[backend.ts] Detection complete for file.py', + ); + }); + + it('should throw error when server responds with error', async () => { + const mockResponse = { + ok: false, + status: 500, + json: jest.fn().mockResolvedValueOnce({ detail: 'Server error' }), + }; + (fetch as jest.Mock).mockResolvedValueOnce(mockResponse); + + await expect(fetchSmells(mockFilePath, mockEnabledSmells)).rejects.toThrow( + 'Backend request failed (500)', + ); + + expect(ecoOutput.error).toHaveBeenCalledWith( + '[backend.ts] Backend error details:', + { detail: 'Server error' }, + ); + }); + + it('should throw error when network fails', async () => { + (fetch as jest.Mock).mockRejectedValueOnce(new Error('Network failed')); + + await expect(fetchSmells(mockFilePath, mockEnabledSmells)).rejects.toThrow( + 'Detection failed: Network failed', + ); + + expect(ecoOutput.error).toHaveBeenCalledWith( + '[backend.ts] Smell detection failed for file.py: Network failed', + ); + }); + }); + + describe('backendRefactorSmell', () => { + it('should successfully refactor smell', async () => { + const mockResponse = { + ok: true, + json: jest.fn().mockResolvedValueOnce({ success: true }), + }; + (fetch as jest.Mock).mockResolvedValueOnce(mockResponse); + + const result = await backendRefactorSmell(mockSmell, mockWorkspacePath); + + expect(fetch).toHaveBeenCalledWith(`http://${mockServerUrl}/refactor`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + sourceDir: mockWorkspacePath, + smell: mockSmell, + }), + }); + expect(result).toEqual({ success: true }); + expect(ecoOutput.info).toHaveBeenCalledWith( + '[backend.ts] Starting refactoring for smell: test-smell', + ); + }); + + it('should throw error when no workspace path', async () => { + await expect(backendRefactorSmell(mockSmell, '')).rejects.toThrow( + 'No workspace path provided', + ); + + expect(ecoOutput.error).toHaveBeenCalledWith( + '[backend.ts] Refactoring aborted: No workspace path', + ); + }); + + it('should throw error when server responds with error', async () => { + const mockResponse = { + ok: false, + json: jest.fn().mockResolvedValueOnce({ detail: 'Refactor failed' }), + }; + (fetch as jest.Mock).mockResolvedValueOnce(mockResponse); + + await expect( + backendRefactorSmell(mockSmell, mockWorkspacePath), + ).rejects.toThrow('Refactoring failed'); + + expect(ecoOutput.error).toHaveBeenCalledWith( + '[backend.ts] Refactoring failed: Refactor failed', + ); + }); + }); + + describe('backendRefactorSmellType', () => { + it('should successfully refactor smell type', async () => { + const mockResponse = { + ok: true, + json: jest.fn().mockResolvedValueOnce({ success: true }), + }; + (fetch as jest.Mock).mockResolvedValueOnce(mockResponse); + + const result = await backendRefactorSmellType(mockSmell, mockWorkspacePath); + + expect(fetch).toHaveBeenCalledWith( + `http://${mockServerUrl}/refactor-by-type`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + sourceDir: mockWorkspacePath, + smellType: 'test-smell', + firstSmell: mockSmell, + }), + }, + ); + expect(result).toEqual({ success: true }); + expect(ecoOutput.info).toHaveBeenCalledWith( + '[backend.ts] Starting refactoring for smells of type "test-smell" in "/project/src/file.py"', + ); + }); + + it('should throw error when no workspace path', async () => { + await expect(backendRefactorSmellType(mockSmell, '')).rejects.toThrow( + 'No workspace path provided', + ); + }); + + it('should throw error when server responds with error', async () => { + const mockResponse = { + ok: false, + json: jest.fn().mockResolvedValueOnce({ detail: 'Type refactor failed' }), + }; + (fetch as jest.Mock).mockResolvedValueOnce(mockResponse); + + await expect( + backendRefactorSmellType(mockSmell, mockWorkspacePath), + ).rejects.toThrow('Type refactor failed'); + }); + }); +}); diff --git a/test/commands/detectSmells.test.ts b/test/commands/detectSmells.test.ts new file mode 100644 index 0000000..8475e64 --- /dev/null +++ b/test/commands/detectSmells.test.ts @@ -0,0 +1,343 @@ +// test/detection.test.ts +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import * as path from 'path'; +import { + detectSmellsFile, + detectSmellsFolder, +} from '../../src/commands/detection/detectSmells'; +import { SmellsViewProvider } from '../../src/providers/SmellsViewProvider'; +import { SmellsCacheManager } from '../../src/context/SmellsCacheManager'; +import { serverStatus, ServerStatusType } from '../../src/emitters/serverStatus'; +import { ecoOutput } from '../../src/extension'; + +import context from '../mocks/context-mock'; + +// Mock the external dependencies +jest.mock('fs'); +jest.mock('path'); +jest.mock('../../src/api/backend'); +jest.mock('../../src/utils/smellsData'); +jest.mock('../../src/providers/SmellsViewProvider'); +jest.mock('../../src/context/SmellsCacheManager'); +jest.mock('../../src/emitters/serverStatus'); +jest.mock('../../src/extension'); + +describe('detectSmellsFile', () => { + let smellsViewProvider: SmellsViewProvider; + let smellsCacheManager: SmellsCacheManager; + const mockFilePath = '/path/to/file.py'; + + beforeEach(() => { + // Reset all mocks before each test + jest.clearAllMocks(); + + // Setup mock instances + smellsViewProvider = new SmellsViewProvider( + context as unknown as vscode.ExtensionContext, + ); + smellsCacheManager = new SmellsCacheManager( + context as unknown as vscode.ExtensionContext, + ); + + // Mock vscode.Uri + (vscode.Uri.file as jest.Mock).mockImplementation((path) => ({ + scheme: 'file', + path, + })); + + // Mock path.basename + (path.basename as jest.Mock).mockImplementation((p) => p.split('/').pop()); + }); + + it('should skip non-file URIs', async () => { + (vscode.Uri.file as jest.Mock).mockReturnValueOnce({ scheme: 'untitled' }); + + await detectSmellsFile(mockFilePath, smellsViewProvider, smellsCacheManager); + + expect(smellsViewProvider.setStatus).not.toHaveBeenCalled(); + expect(vscode.window.showErrorMessage).not.toHaveBeenCalled(); + }); + + it('should skip non-Python files', async () => { + const nonPythonPath = '/path/to/file.txt'; + + await detectSmellsFile(nonPythonPath, smellsViewProvider, smellsCacheManager); + + expect(smellsViewProvider.setStatus).not.toHaveBeenCalled(); + expect(vscode.window.showErrorMessage).not.toHaveBeenCalled(); + }); + + it('should use cached smells when available', async () => { + const mockCachedSmells = [{ id: 'smell1' }]; + (smellsCacheManager.hasCachedSmells as jest.Mock).mockReturnValue(true); + (smellsCacheManager.getCachedSmells as jest.Mock).mockReturnValue( + mockCachedSmells, + ); + + await detectSmellsFile(mockFilePath, smellsViewProvider, smellsCacheManager); + + expect(ecoOutput.info).toHaveBeenCalledWith( + expect.stringContaining('Using cached results'), + ); + expect(smellsViewProvider.setStatus).toHaveBeenCalledWith( + mockFilePath, + 'passed', + ); + expect(smellsViewProvider.setSmells).toHaveBeenCalledWith( + mockFilePath, + mockCachedSmells, + ); + }); + + it('should handle server down state', async () => { + (serverStatus.getStatus as jest.Mock).mockReturnValue(ServerStatusType.DOWN); + (smellsCacheManager.hasCachedSmells as jest.Mock).mockReturnValue(false); + + await detectSmellsFile(mockFilePath, smellsViewProvider, smellsCacheManager); + + expect(vscode.window.showWarningMessage).toHaveBeenCalled(); + expect(smellsViewProvider.setStatus).toHaveBeenCalledWith( + mockFilePath, + 'server_down', + ); + }); + + it('should warn when no smells are enabled', async () => { + (serverStatus.getStatus as jest.Mock).mockReturnValue(ServerStatusType.UP); + (smellsCacheManager.hasCachedSmells as jest.Mock).mockReturnValue(false); + ( + require('../../src/utils/smellsData').getEnabledSmells as jest.Mock + ).mockReturnValue({}); + + await detectSmellsFile(mockFilePath, smellsViewProvider, smellsCacheManager); + + expect(vscode.window.showWarningMessage).toHaveBeenCalledWith( + 'No smell detectors enabled in settings', + ); + expect(smellsViewProvider.setStatus).not.toHaveBeenCalledWith( + mockFilePath, + 'queued', + ); + }); + + it('should fetch and process smells successfully', async () => { + const mockSmells = [{ id: 'smell1' }]; + (serverStatus.getStatus as jest.Mock).mockReturnValue(ServerStatusType.UP); + (smellsCacheManager.hasCachedSmells as jest.Mock).mockReturnValue(false); + ( + require('../../src/utils/smellsData').getEnabledSmells as jest.Mock + ).mockReturnValue({ + smell1: { options: {} }, + }); + (require('../../src/api/backend').fetchSmells as jest.Mock).mockResolvedValue({ + smells: mockSmells, + status: 200, + }); + + await detectSmellsFile(mockFilePath, smellsViewProvider, smellsCacheManager); + + expect(smellsViewProvider.setStatus).toHaveBeenCalledWith( + mockFilePath, + 'queued', + ); + expect(smellsCacheManager.setCachedSmells).toHaveBeenCalledWith( + mockFilePath, + mockSmells, + ); + expect(smellsViewProvider.setSmells).toHaveBeenCalledWith( + mockFilePath, + mockSmells, + ); + expect(smellsViewProvider.setStatus).toHaveBeenCalledWith( + mockFilePath, + 'passed', + ); + expect(ecoOutput.info).toHaveBeenCalledWith( + expect.stringContaining('Detected 1 smells'), + ); + }); + + it('should handle no smells found', async () => { + (serverStatus.getStatus as jest.Mock).mockReturnValue(ServerStatusType.UP); + (smellsCacheManager.hasCachedSmells as jest.Mock).mockReturnValue(false); + ( + require('../../src/utils/smellsData').getEnabledSmells as jest.Mock + ).mockReturnValue({ + smell1: { options: {} }, + }); + (require('../../src/api/backend').fetchSmells as jest.Mock).mockResolvedValue({ + smells: [], + status: 200, + }); + + await detectSmellsFile(mockFilePath, smellsViewProvider, smellsCacheManager); + + expect(smellsViewProvider.setStatus).toHaveBeenCalledWith( + mockFilePath, + 'no_issues', + ); + expect(smellsCacheManager.setCachedSmells).toHaveBeenCalledWith( + mockFilePath, + [], + ); + expect(ecoOutput.info).toHaveBeenCalledWith( + expect.stringContaining('File has no detectable smells'), + ); + }); + + it('should handle API errors', async () => { + (serverStatus.getStatus as jest.Mock).mockReturnValue(ServerStatusType.UP); + (smellsCacheManager.hasCachedSmells as jest.Mock).mockReturnValue(false); + ( + require('../../src/utils/smellsData').getEnabledSmells as jest.Mock + ).mockReturnValue({ + smell1: { options: {} }, + }); + (require('../../src/api/backend').fetchSmells as jest.Mock).mockResolvedValue({ + smells: [], + status: 500, + }); + + await detectSmellsFile(mockFilePath, smellsViewProvider, smellsCacheManager); + + expect(vscode.window.showErrorMessage).toHaveBeenCalled(); + expect(smellsViewProvider.setStatus).toHaveBeenCalledWith( + mockFilePath, + 'failed', + ); + }); + + it('should handle unexpected errors', async () => { + (serverStatus.getStatus as jest.Mock).mockReturnValue(ServerStatusType.UP); + (smellsCacheManager.hasCachedSmells as jest.Mock).mockReturnValue(false); + ( + require('../../src/utils/smellsData').getEnabledSmells as jest.Mock + ).mockReturnValue({ + smell1: { options: {} }, + }); + (require('../../src/api/backend').fetchSmells as jest.Mock).mockRejectedValue( + new Error('API failed'), + ); + + await detectSmellsFile(mockFilePath, smellsViewProvider, smellsCacheManager); + + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith( + 'Analysis failed: API failed', + ); + expect(smellsViewProvider.setStatus).toHaveBeenCalledWith( + mockFilePath, + 'failed', + ); + expect(ecoOutput.error).toHaveBeenCalled(); + }); +}); + +describe('detectSmellsFolder', () => { + let smellsViewProvider: SmellsViewProvider; + let smellsCacheManager: SmellsCacheManager; + const mockFolderPath = '/path/to/folder'; + + beforeEach(() => { + jest.clearAllMocks(); + + smellsViewProvider = new SmellsViewProvider( + context as unknown as vscode.ExtensionContext, + ); + smellsCacheManager = new SmellsCacheManager( + context as unknown as vscode.ExtensionContext, + ); + + // Mock vscode.window.withProgress + (vscode.window.withProgress as jest.Mock).mockImplementation((_, callback) => { + return callback(); + }); + + // Mock path.basename + (path.basename as jest.Mock).mockImplementation((p) => p.split('/').pop()); + }); + + it('should show progress notification', async () => { + (fs.readdirSync as jest.Mock).mockReturnValue([]); + + await detectSmellsFolder(mockFolderPath, smellsViewProvider, smellsCacheManager); + + expect(vscode.window.withProgress).toHaveBeenCalled(); + }); + + it('should handle empty folder', async () => { + (fs.readdirSync as jest.Mock).mockReturnValue([]); + + await detectSmellsFolder(mockFolderPath, smellsViewProvider, smellsCacheManager); + + expect(vscode.window.showWarningMessage).toHaveBeenCalledWith( + expect.stringContaining('No Python files found'), + ); + expect(ecoOutput.info).toHaveBeenCalledWith( + expect.stringContaining('Found 0 files to analyze'), + ); + }); + + it('should process Python files in folder', async () => { + const mockFiles = ['file1.py', 'subdir/file2.py', 'ignore.txt']; + + (fs.readdirSync as jest.Mock).mockImplementation((dir) => { + if (dir === mockFolderPath) return mockFiles; + if (dir === mockFolderPath + 'subdir') return ['file2.py']; + console.log('Here'); + return mockFiles; + }); + + jest + .spyOn(fs, 'statSync') + .mockReturnValueOnce({ + isDirectory: (): boolean => false, + isFile: (): boolean => true, + } as unknown as fs.Stats) + .mockReturnValueOnce({ + isDirectory: (): boolean => true, + } as unknown as fs.Stats) + .mockReturnValueOnce({ + isDirectory: (): boolean => false, + isFile: (): boolean => true, + } as unknown as fs.Stats) + .mockReturnValueOnce({ + isDirectory: (): boolean => false, + isFile: (): boolean => true, + } as unknown as fs.Stats); + + jest + .spyOn(String.prototype, 'endsWith') + .mockReturnValueOnce(true) + .mockReturnValueOnce(true) + .mockReturnValueOnce(false); + + jest + .spyOn(path, 'join') + .mockReturnValueOnce(mockFolderPath + '/file1.py') + .mockReturnValueOnce(mockFolderPath + '/subdir') + .mockReturnValueOnce(mockFolderPath + '/subdir' + '/file2.py') + .mockReturnValueOnce(mockFolderPath + 'ignore.txt'); + + await detectSmellsFolder(mockFolderPath, smellsViewProvider, smellsCacheManager); + + expect(vscode.window.showInformationMessage).toHaveBeenCalledWith( + 'Analyzing 2 Python files...', + ); + expect(ecoOutput.info).toHaveBeenCalledWith( + expect.stringContaining('Found 2 files to analyze'), + ); + }); + + it('should handle directory scan errors', async () => { + (fs.readdirSync as jest.Mock).mockImplementation(() => { + throw new Error('Permission denied'); + }); + + await detectSmellsFolder(mockFolderPath, smellsViewProvider, smellsCacheManager); + + expect(ecoOutput.error).toHaveBeenCalledWith( + expect.stringContaining('Scan error: Permission denied'), + ); + }); +}); diff --git a/test/commands/exportMetricsData.test.ts b/test/commands/exportMetricsData.test.ts new file mode 100644 index 0000000..84a59ba --- /dev/null +++ b/test/commands/exportMetricsData.test.ts @@ -0,0 +1,186 @@ +// test/exportMetrics.test.ts +import * as vscode from 'vscode'; +import { dirname } from 'path'; +import { writeFileSync } from 'fs'; +import { exportMetricsData } from '../../src/commands/views/exportMetricsData'; +import { envConfig } from '../../src/utils/envConfig'; +import * as fs from 'fs'; + +// Mock dependencies +jest.mock('path'); +jest.mock('fs'); + +describe('exportMetricsData', () => { + let mockContext: vscode.ExtensionContext; + const mockMetricsData = { + '/path/to/file1.py': { + energySaved: 0.5, + smellType: 'test-smell', + timestamp: Date.now(), + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + + // Setup mock context + mockContext = { + workspaceState: { + get: jest.fn(), + update: jest.fn(), + }, + } as unknown as vscode.ExtensionContext; + + // Mock path.dirname + (dirname as jest.Mock).mockImplementation((path) => `/parent/${path}`); + + // Mock fs.writeFileSync + jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {}); + }); + + it('should show info message when no metrics data exists', async () => { + (mockContext.workspaceState.get as jest.Mock).mockImplementation((key) => { + console.log('Mock:', key, envConfig.WORKSPACE_METRICS_DATA); + if (key === envConfig.WORKSPACE_METRICS_DATA) return {}; + return undefined; + }); + + await exportMetricsData(mockContext); + + expect(vscode.window.showInformationMessage).toHaveBeenCalledWith( + 'No metrics data available to export.', + ); + }); + + it('should show error when no workspace path is configured', async () => { + (mockContext.workspaceState.get as jest.Mock).mockImplementation((key) => { + if (key === envConfig.WORKSPACE_CONFIGURED_PATH) return undefined; + if (key === envConfig.WORKSPACE_METRICS_DATA) return mockMetricsData; + return undefined; // No workspace path + }); + + await exportMetricsData(mockContext); + + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith( + 'No configured workspace path found.', + ); + }); + + it('should export to workspace directory when path is a directory', async () => { + const workspacePath = '/workspace/path'; + + (mockContext.workspaceState.get as jest.Mock).mockImplementation((key) => { + if (key === envConfig.WORKSPACE_METRICS_DATA) return mockMetricsData; + if (key === envConfig.WORKSPACE_CONFIGURED_PATH) return workspacePath; + return undefined; + }); + + // Mock fs.stat to return directory + (vscode.workspace.fs.stat as jest.Mock).mockResolvedValue({ + type: vscode.FileType.Directory, + }); + + await exportMetricsData(mockContext); + + expect(vscode.Uri.joinPath).toHaveBeenCalledWith( + expect.anything(), + 'metrics-data.json', + ); + expect(writeFileSync).toHaveBeenCalled(); + expect(vscode.window.showInformationMessage).toHaveBeenCalledWith( + expect.stringContaining('metrics-data.json'), + ); + }); + + it('should export to parent directory when path is a file', async () => { + const workspacePath = '/workspace/path/file.txt'; + + (mockContext.workspaceState.get as jest.Mock).mockImplementation((key) => { + if (key === envConfig.WORKSPACE_METRICS_DATA) return mockMetricsData; + if (key === envConfig.WORKSPACE_CONFIGURED_PATH) return workspacePath; + return undefined; + }); + + // Mock fs.stat to return file + (vscode.workspace.fs.stat as jest.Mock).mockResolvedValue({ + type: vscode.FileType.File, + }); + + await exportMetricsData(mockContext); + + expect(dirname).toHaveBeenCalledWith(workspacePath); + expect(vscode.Uri.joinPath).toHaveBeenCalledWith( + expect.anything(), + 'metrics-data.json', + ); + expect(writeFileSync).toHaveBeenCalled(); + }); + + it('should show error for invalid workspace path type', async () => { + const workspacePath = '/workspace/path'; + + (mockContext.workspaceState.get as jest.Mock).mockImplementation((key) => { + if (key === envConfig.WORKSPACE_METRICS_DATA) return mockMetricsData; + if (key === envConfig.WORKSPACE_CONFIGURED_PATH) return workspacePath; + return undefined; + }); + + // Mock fs.stat to return unknown type + (vscode.workspace.fs.stat as jest.Mock).mockResolvedValue({ + type: vscode.FileType.Unknown, + }); + + await exportMetricsData(mockContext); + + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith( + 'Invalid workspace path type.', + ); + }); + + it('should handle filesystem access errors', async () => { + const workspacePath = '/workspace/path'; + + (mockContext.workspaceState.get as jest.Mock).mockImplementation((key) => { + if (key === envConfig.WORKSPACE_METRICS_DATA) return mockMetricsData; + if (key === envConfig.WORKSPACE_CONFIGURED_PATH) return workspacePath; + return undefined; + }); + + // Mock fs.stat to throw error + (vscode.workspace.fs.stat as jest.Mock).mockRejectedValue( + new Error('Access denied'), + ); + + await exportMetricsData(mockContext); + + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith( + expect.stringContaining('Failed to access workspace path'), + ); + }); + + it('should handle file write errors', async () => { + const workspacePath = '/workspace/path'; + + (mockContext.workspaceState.get as jest.Mock).mockImplementation((key) => { + if (key === envConfig.WORKSPACE_METRICS_DATA) return mockMetricsData; + if (key === envConfig.WORKSPACE_CONFIGURED_PATH) return workspacePath; + return undefined; + }); + + // Mock fs.stat to return directory + (vscode.workspace.fs.stat as jest.Mock).mockResolvedValue({ + type: vscode.FileType.Directory, + }); + + // Mock writeFileSync to throw error + (writeFileSync as jest.Mock).mockImplementation(() => { + throw new Error('Write failed'); + }); + + await exportMetricsData(mockContext); + + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith( + expect.stringContaining('Failed to export metrics data'), + ); + }); +}); diff --git a/test/commands/filterSmells.test.ts b/test/commands/filterSmells.test.ts new file mode 100644 index 0000000..5939e58 --- /dev/null +++ b/test/commands/filterSmells.test.ts @@ -0,0 +1,150 @@ +// test/commands/registerFilterSmellCommands.test.ts +import * as vscode from 'vscode'; +import { registerFilterSmellCommands } from '../../src/commands/views/filterSmells'; +import { FilterViewProvider } from '../../src/providers/FilterViewProvider'; + +// Mock the FilterViewProvider +jest.mock('../../src/providers/FilterViewProvider'); + +describe('registerFilterSmellCommands', () => { + let mockContext: vscode.ExtensionContext; + let mockFilterProvider: jest.Mocked; + let mockCommands: jest.Mocked; + + beforeEach(() => { + jest.clearAllMocks(); + + // Setup mock context + mockContext = { + subscriptions: [], + } as unknown as vscode.ExtensionContext; + + // Setup mock filter provider + mockFilterProvider = { + toggleSmell: jest.fn(), + updateOption: jest.fn(), + refresh: jest.fn(), + setAllSmellsEnabled: jest.fn(), + resetToDefaults: jest.fn(), + } as unknown as jest.Mocked; + + // Mock commands + mockCommands = vscode.commands as jest.Mocked; + }); + + it('should register toggleSmellFilter command', () => { + registerFilterSmellCommands(mockContext, mockFilterProvider); + + // Verify command registration + expect(mockCommands.registerCommand).toHaveBeenCalledWith( + 'ecooptimizer.toggleSmellFilter', + expect.any(Function), + ); + + // Test the command handler + const [, handler] = (mockCommands.registerCommand as jest.Mock).mock.calls[0]; + handler('test-smell'); + expect(mockFilterProvider.toggleSmell).toHaveBeenCalledWith('test-smell'); + }); + + it('should register editSmellFilterOption command with valid input', async () => { + registerFilterSmellCommands(mockContext, mockFilterProvider); + + // Mock showInputBox to return valid number + (vscode.window.showInputBox as jest.Mock).mockResolvedValue('42'); + + // Get the command handler + const editCommandCall = ( + mockCommands.registerCommand as jest.Mock + ).mock.calls.find((call) => call[0] === 'ecooptimizer.editSmellFilterOption'); + const [, handler] = editCommandCall; + + // Test with valid item + await handler({ smellKey: 'test-smell', optionKey: 'threshold', value: 10 }); + + expect(vscode.window.showInputBox).toHaveBeenCalledWith({ + prompt: 'Enter a new value for threshold', + value: '10', + validateInput: expect.any(Function), + }); + expect(mockFilterProvider.updateOption).toHaveBeenCalledWith( + 'test-smell', + 'threshold', + 42, + ); + expect(mockFilterProvider.refresh).toHaveBeenCalled(); + }); + + it('should handle editSmellFilterOption with invalid input', async () => { + registerFilterSmellCommands(mockContext, mockFilterProvider); + + // Mock showInputBox to return invalid input + (vscode.window.showInputBox as jest.Mock).mockResolvedValue('not-a-number'); + + const editCommandCall = ( + mockCommands.registerCommand as jest.Mock + ).mock.calls.find((call) => call[0] === 'ecooptimizer.editSmellFilterOption'); + const [, handler] = editCommandCall; + + await handler({ smellKey: 'test-smell', optionKey: 'threshold', value: 10 }); + + expect(mockFilterProvider.updateOption).not.toHaveBeenCalled(); + }); + + it('should show error for editSmellFilterOption with missing keys', async () => { + registerFilterSmellCommands(mockContext, mockFilterProvider); + + const editCommandCall = ( + mockCommands.registerCommand as jest.Mock + ).mock.calls.find((call) => call[0] === 'ecooptimizer.editSmellFilterOption'); + const [, handler] = editCommandCall; + + await handler({}); + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith( + 'Error: Missing smell or option key.', + ); + }); + + it('should register selectAllFilterSmells command', () => { + registerFilterSmellCommands(mockContext, mockFilterProvider); + + const selectAllCall = ( + mockCommands.registerCommand as jest.Mock + ).mock.calls.find((call) => call[0] === 'ecooptimizer.selectAllFilterSmells'); + const [, handler] = selectAllCall; + + handler(); + expect(mockFilterProvider.setAllSmellsEnabled).toHaveBeenCalledWith(true); + }); + + it('should register deselectAllFilterSmells command', () => { + registerFilterSmellCommands(mockContext, mockFilterProvider); + + const deselectAllCall = ( + mockCommands.registerCommand as jest.Mock + ).mock.calls.find((call) => call[0] === 'ecooptimizer.deselectAllFilterSmells'); + const [, handler] = deselectAllCall; + + handler(); + expect(mockFilterProvider.setAllSmellsEnabled).toHaveBeenCalledWith(false); + }); + + it('should register setFilterDefaults command', () => { + registerFilterSmellCommands(mockContext, mockFilterProvider); + + const setDefaultsCall = ( + mockCommands.registerCommand as jest.Mock + ).mock.calls.find((call) => call[0] === 'ecooptimizer.setFilterDefaults'); + const [, handler] = setDefaultsCall; + + handler(); + expect(mockFilterProvider.resetToDefaults).toHaveBeenCalled(); + }); + + it('should add all commands to context subscriptions', () => { + registerFilterSmellCommands(mockContext, mockFilterProvider); + + // Verify all commands were added to subscriptions + expect(mockContext.subscriptions).toHaveLength(5); + }); +}); diff --git a/test/commands/refactorSmell.test.ts b/test/commands/refactorSmell.test.ts new file mode 100644 index 0000000..451492d --- /dev/null +++ b/test/commands/refactorSmell.test.ts @@ -0,0 +1,402 @@ +// test/refactor.test.ts +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import * as path from 'path'; +import { + refactor, + startRefactorSession, +} from '../../src/commands/refactor/refactor'; +import { SmellsViewProvider } from '../../src/providers/SmellsViewProvider'; +import { RefactoringDetailsViewProvider } from '../../src/providers/RefactoringDetailsViewProvider'; +import { serverStatus, ServerStatusType } from '../../src/emitters/serverStatus'; +import { ecoOutput } from '../../src/extension'; +import { envConfig } from '../../src/utils/envConfig'; +import context from '../mocks/context-mock'; +import { MetricsViewProvider } from '../../src/providers/MetricsViewProvider'; +import { SmellsCacheManager } from '../../src/context/SmellsCacheManager'; +import { acceptRefactoring } from '../../src/commands/refactor/acceptRefactoring'; +import { rejectRefactoring } from '../../src/commands/refactor/rejectRefactoring'; + +// Mock all external dependencies +jest.mock('vscode'); +jest.mock('path'); +jest.mock('fs'); +jest.mock('../../src/api/backend'); +jest.mock('../../src/providers/SmellsViewProvider'); +jest.mock('../../src/providers/RefactoringDetailsViewProvider'); +jest.mock('../../src/emitters/serverStatus'); +jest.mock('../../src/extension'); +jest.mock('../../src/utils/refactorActionButtons'); +jest.mock('../../src/utils/trackedDiffEditors'); + +const mockContext = context as unknown as vscode.ExtensionContext; + +describe('refactor', () => { + let smellsViewProvider: SmellsViewProvider; + let refactoringDetailsViewProvider: RefactoringDetailsViewProvider; + const mockSmell = { + symbol: 'testSmell', + path: '/path/to/file.py', + type: 'testType', + } as unknown as Smell; + + beforeEach(() => { + jest.clearAllMocks(); + + smellsViewProvider = new SmellsViewProvider({} as vscode.ExtensionContext); + refactoringDetailsViewProvider = new RefactoringDetailsViewProvider(); + + (path.basename as jest.Mock).mockImplementation((p) => p.split('/').pop()); + + (serverStatus.getStatus as jest.Mock).mockReturnValue(ServerStatusType.UP); + + context.workspaceState.get.mockImplementation((key: string) => { + if (key === envConfig.WORKSPACE_CONFIGURED_PATH) { + return '/workspace/path'; + } + return undefined; + }); + }); + + it('should show error when no workspace is configured', async () => { + (context.workspaceState.get as jest.Mock).mockReturnValue(undefined); + + await refactor( + smellsViewProvider, + refactoringDetailsViewProvider, + mockSmell, + mockContext, + ); + + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith( + 'Please configure workspace first', + ); + expect(ecoOutput.error).toHaveBeenCalledWith( + expect.stringContaining('Refactoring aborted: No workspace configured'), + ); + }); + + it('should show warning when backend is down', async () => { + (serverStatus.getStatus as jest.Mock).mockReturnValue(ServerStatusType.DOWN); + + await refactor( + smellsViewProvider, + refactoringDetailsViewProvider, + mockSmell, + mockContext, + ); + + expect(vscode.window.showWarningMessage).toHaveBeenCalledWith( + 'Cannot refactor - backend service unavailable', + ); + expect(smellsViewProvider.setStatus).toHaveBeenCalledWith( + mockSmell.path, + 'server_down', + ); + }); + + it('should initiate single smell refactoring', async () => { + const mockRefactoredData = { + targetFile: { + original: '/original/path', + refactored: '/refactored/path', + }, + affectedFiles: [], + energySaved: 0.5, + tempDir: '/temp/dir', + }; + + ( + require('../../src/api/backend').backendRefactorSmell as jest.Mock + ).mockResolvedValue(mockRefactoredData); + + await refactor( + smellsViewProvider, + refactoringDetailsViewProvider, + mockSmell, + mockContext, + ); + + expect(vscode.window.showInformationMessage).toHaveBeenCalledWith( + expect.stringContaining('Refactoring testSmell...'), + ); + expect(smellsViewProvider.setStatus).toHaveBeenCalledWith( + mockSmell.path, + 'queued', + ); + expect(mockContext.workspaceState.update).toHaveBeenCalled(); + expect(ecoOutput.info).toHaveBeenCalledWith( + expect.stringContaining('Refactoring completed for file.py'), + ); + }); + + it('should initiate refactoring all smells of type', async () => { + const mockRefactoredData = { + targetFile: { + original: '/original/path', + refactored: '/refactored/path', + }, + affectedFiles: [], + energySaved: 1.2, + tempDir: '/temp/dir', + }; + + ( + require('../../src/api/backend').backendRefactorSmellType as jest.Mock + ).mockResolvedValue(mockRefactoredData); + + await refactor( + smellsViewProvider, + refactoringDetailsViewProvider, + mockSmell, + mockContext, + true, + ); + + expect(vscode.window.showInformationMessage).toHaveBeenCalledWith( + expect.stringContaining('Refactoring all smells of type testSmell...'), + ); + expect( + require('../../src/api/backend').backendRefactorSmellType, + ).toHaveBeenCalled(); + }); + + it('should handle refactoring failure', async () => { + const error = new Error('Backend error'); + ( + require('../../src/api/backend').backendRefactorSmell as jest.Mock + ).mockRejectedValue(error); + + await refactor( + smellsViewProvider, + refactoringDetailsViewProvider, + mockSmell, + mockContext, + ); + + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith( + 'Refactoring failed. See output for details.', + ); + expect(ecoOutput.error).toHaveBeenCalledWith( + expect.stringContaining('Refactoring failed: Backend error'), + ); + expect( + refactoringDetailsViewProvider.resetRefactoringDetails, + ).toHaveBeenCalled(); + expect( + require('../../src/utils/refactorActionButtons').hideRefactorActionButtons, + ).toHaveBeenCalled(); + expect(smellsViewProvider.setStatus).toHaveBeenCalledWith( + mockSmell.path, + 'failed', + ); + }); + + describe('startRefactorSession', () => { + let refactoringDetailsViewProvider: RefactoringDetailsViewProvider; + const mockSmell = { + symbol: 'testSmell', + path: 'original/path/to/file.py', + } as unknown as Smell; + const mockRefactoredData = { + targetFile: { + original: 'original/path/to/file.py', + refactored: 'refactored/path/to/file.py', + }, + affectedFiles: [], + energySaved: 0.5, + tempDir: '/refactored', + }; + + beforeEach(() => { + jest.clearAllMocks(); + refactoringDetailsViewProvider = new RefactoringDetailsViewProvider(); + + // Mock path.basename + (path.basename as jest.Mock).mockImplementation((p) => p.split('/').pop()); + + // Mock vscode.Uri.file + (vscode.Uri.file as jest.Mock).mockImplementation((path) => ({ path })); + }); + + it('should update refactoring details and show diff', async () => { + await startRefactorSession( + mockSmell, + mockRefactoredData, + refactoringDetailsViewProvider, + ); + + expect( + refactoringDetailsViewProvider.updateRefactoringDetails, + ).toHaveBeenCalledWith( + mockSmell, + mockRefactoredData.targetFile, + mockRefactoredData.affectedFiles, + mockRefactoredData.energySaved, + ); + + expect(vscode.commands.executeCommand).toHaveBeenCalledWith( + 'vscode.diff', + expect.anything(), + expect.anything(), + 'Refactoring Comparison (file.py)', + { preview: false }, + ); + + expect( + require('../../src/utils/trackedDiffEditors').registerDiffEditor, + ).toHaveBeenCalled(); + + expect(vscode.commands.executeCommand).toHaveBeenCalledWith( + 'ecooptimizer.refactorView.focus', + ); + + expect( + require('../../src/utils/refactorActionButtons').showRefactorActionButtons, + ).toHaveBeenCalled(); + + expect(vscode.window.showInformationMessage).toHaveBeenCalledWith( + 'Refactoring complete. Estimated savings: 0.5 kg CO2', + ); + }); + + it('should handle missing energy data', async () => { + const dataWithoutEnergy = { ...mockRefactoredData, energySaved: undefined }; + + await startRefactorSession( + mockSmell, + dataWithoutEnergy, + refactoringDetailsViewProvider, + ); + + expect(vscode.window.showInformationMessage).toHaveBeenCalledWith( + 'Refactoring complete. Estimated savings: N/A kg CO2', + ); + }); + }); + + describe('acceptRefactoring', () => { + let metricsDataProvider: { updateMetrics: jest.Mock }; + let smellsCacheManager: { clearCachedSmellsForFile: jest.Mock }; + + beforeEach(() => { + metricsDataProvider = { + updateMetrics: jest.fn(), + }; + smellsCacheManager = { + clearCachedSmellsForFile: jest.fn(), + }; + + // Mock refactoring details + refactoringDetailsViewProvider.targetFile = { + original: '/original/path', + refactored: '/refactored/path', + }; + refactoringDetailsViewProvider.affectedFiles = [ + { original: '/affected/original', refactored: '/affected/refactored' }, + ]; + refactoringDetailsViewProvider.energySaved = 0.5; + refactoringDetailsViewProvider.targetSmell = mockSmell; + }); + + it('should apply refactoring changes successfully', async () => { + await acceptRefactoring( + mockContext, + refactoringDetailsViewProvider, + metricsDataProvider as unknown as MetricsViewProvider, + smellsCacheManager as unknown as SmellsCacheManager, + smellsViewProvider, + ); + + expect(fs.copyFileSync).toHaveBeenCalledTimes(2); + expect(metricsDataProvider.updateMetrics).toHaveBeenCalled(); + expect(smellsCacheManager.clearCachedSmellsForFile).toHaveBeenCalledTimes(2); + expect(smellsViewProvider.setStatus).toHaveBeenCalledWith( + '/original/path', + 'outdated', + ); + expect(vscode.window.showInformationMessage).toHaveBeenCalledWith( + 'Refactoring successfully applied', + ); + }); + + it('should handle missing refactoring data', async () => { + refactoringDetailsViewProvider.targetFile = undefined; + + await acceptRefactoring( + mockContext, + refactoringDetailsViewProvider, + metricsDataProvider as unknown as MetricsViewProvider, + smellsCacheManager as unknown as SmellsCacheManager, + smellsViewProvider, + ); + + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith( + 'No refactoring data available.', + ); + }); + + it('should handle filesystem errors', async () => { + (fs.copyFileSync as jest.Mock).mockImplementation(() => { + throw new Error('Filesystem error'); + }); + + await acceptRefactoring( + mockContext, + refactoringDetailsViewProvider, + metricsDataProvider as unknown as MetricsViewProvider, + smellsCacheManager as unknown as SmellsCacheManager, + smellsViewProvider, + ); + + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith( + 'Failed to apply refactoring. Please try again.', + ); + }); + }); + + describe('rejectRefactoring', () => { + beforeEach(() => { + refactoringDetailsViewProvider.targetFile = { + original: '/original/path', + refactored: '/refactored/path', + }; + }); + + it('should clean up after rejecting refactoring', async () => { + await rejectRefactoring( + mockContext, + refactoringDetailsViewProvider, + smellsViewProvider, + ); + + expect(smellsViewProvider.setStatus).toHaveBeenCalledWith( + '/original/path', + 'passed', + ); + expect(vscode.window.showInformationMessage).toHaveBeenCalledWith( + 'Refactoring changes discarded', + ); + expect(mockContext.workspaceState.update).toHaveBeenCalledWith( + envConfig.UNFINISHED_REFACTORING!, + undefined, + ); + }); + + it('should handle errors during cleanup', async () => { + (smellsViewProvider.setStatus as jest.Mock).mockImplementation(() => { + throw new Error('Status update failed'); + }); + + await rejectRefactoring( + mockContext, + refactoringDetailsViewProvider, + smellsViewProvider, + ); + + expect(ecoOutput.error).toHaveBeenCalledWith( + expect.stringContaining('Error during rejection cleanup'), + ); + }); + }); +}); diff --git a/test/commands/wipeWorkCache.test.ts b/test/commands/wipeWorkCache.test.ts new file mode 100644 index 0000000..ac85d1b --- /dev/null +++ b/test/commands/wipeWorkCache.test.ts @@ -0,0 +1,130 @@ +// test/wipeWorkCache.test.ts +import * as vscode from 'vscode'; +import { SmellsCacheManager } from '../../src/context/SmellsCacheManager'; +import { SmellsViewProvider } from '../../src/providers/SmellsViewProvider'; +import { wipeWorkCache } from '../../src/commands/detection/wipeWorkCache'; +import context from '../mocks/context-mock'; + +// Mock the external dependencies +jest.mock('vscode'); +jest.mock('../../src/context/SmellsCacheManager'); +jest.mock('../../src/providers/SmellsViewProvider'); + +describe('wipeWorkCache', () => { + let smellsCacheManager: SmellsCacheManager; + let smellsViewProvider: SmellsViewProvider; + + beforeEach(() => { + // Reset all mocks before each test + jest.clearAllMocks(); + + // Setup mock instances + smellsCacheManager = new SmellsCacheManager( + context as unknown as vscode.ExtensionContext, + ); + smellsViewProvider = new SmellsViewProvider( + context as unknown as vscode.ExtensionContext, + ); + + // Mock the showWarningMessage to return undefined by default + (vscode.window.showWarningMessage as jest.Mock).mockResolvedValue(undefined); + }); + + it('should show confirmation dialog before clearing cache', async () => { + await wipeWorkCache(smellsCacheManager, smellsViewProvider); + + expect(vscode.window.showWarningMessage).toHaveBeenCalledWith( + 'Are you sure you want to clear the entire workspace analysis? This action cannot be undone.', + { modal: true }, + 'Confirm', + ); + }); + + it('should clear cache and refresh UI when user confirms', async () => { + // Mock user confirming the action + (vscode.window.showWarningMessage as jest.Mock).mockResolvedValue('Confirm'); + + await wipeWorkCache(smellsCacheManager, smellsViewProvider); + + expect(smellsCacheManager.clearAllCachedSmells).toHaveBeenCalled(); + expect(smellsViewProvider.clearAllStatuses).toHaveBeenCalled(); + expect(smellsViewProvider.refresh).toHaveBeenCalled(); + expect(vscode.window.showInformationMessage).toHaveBeenCalledWith( + 'Workspace analysis cleared successfully.', + ); + }); + + it('should not clear cache when user cancels', async () => { + // Mock user cancelling the action + (vscode.window.showWarningMessage as jest.Mock).mockResolvedValue(undefined); + + await wipeWorkCache(smellsCacheManager, smellsViewProvider); + + expect(smellsCacheManager.clearAllCachedSmells).not.toHaveBeenCalled(); + expect(smellsViewProvider.clearAllStatuses).not.toHaveBeenCalled(); + expect(smellsViewProvider.refresh).not.toHaveBeenCalled(); + expect(vscode.window.showInformationMessage).toHaveBeenCalledWith( + 'Operation cancelled.', + ); + }); + + it('should not clear cache when user dismisses dialog', async () => { + // Mock user dismissing the dialog (different from cancelling) + (vscode.window.showWarningMessage as jest.Mock).mockResolvedValue(undefined); + + await wipeWorkCache(smellsCacheManager, smellsViewProvider); + + expect(smellsCacheManager.clearAllCachedSmells).not.toHaveBeenCalled(); + expect(smellsViewProvider.clearAllStatuses).not.toHaveBeenCalled(); + expect(smellsViewProvider.refresh).not.toHaveBeenCalled(); + expect(vscode.window.showInformationMessage).toHaveBeenCalledWith( + 'Operation cancelled.', + ); + }); + + it('should handle case where user clicks something other than Confirm', async () => { + // Mock user clicking something else (e.g., a different button if more were added) + (vscode.window.showWarningMessage as jest.Mock).mockResolvedValue( + 'Other Option', + ); + + await wipeWorkCache(smellsCacheManager, smellsViewProvider); + + expect(smellsCacheManager.clearAllCachedSmells).not.toHaveBeenCalled(); + expect(smellsViewProvider.clearAllStatuses).not.toHaveBeenCalled(); + expect(smellsViewProvider.refresh).not.toHaveBeenCalled(); + expect(vscode.window.showInformationMessage).toHaveBeenCalledWith( + 'Operation cancelled.', + ); + }); + + it('should show success message only after successful cache clearing', async () => { + // Mock user confirming the action + (vscode.window.showWarningMessage as jest.Mock).mockResolvedValue('Confirm'); + + // Mock a successful cache clearing + (smellsCacheManager.clearAllCachedSmells as jest.Mock).mockImplementation(() => { + // Simulate successful clearing + }); + + await wipeWorkCache(smellsCacheManager, smellsViewProvider); + + expect(vscode.window.showInformationMessage).toHaveBeenCalledWith( + 'Workspace analysis cleared successfully.', + ); + }); + + it('should still show cancellation message if confirmation is aborted', async () => { + // Simulate the confirmation dialog being closed without any selection + (vscode.window.showWarningMessage as jest.Mock).mockResolvedValue(undefined); + + await wipeWorkCache(smellsCacheManager, smellsViewProvider); + + expect(vscode.window.showInformationMessage).toHaveBeenCalledWith( + 'Operation cancelled.', + ); + expect(vscode.window.showInformationMessage).not.toHaveBeenCalledWith( + 'Workspace analysis cleared successfully.', + ); + }); +}); diff --git a/test/listeners/workspaceModifiedListener.test.ts b/test/listeners/workspaceModifiedListener.test.ts new file mode 100644 index 0000000..8d90dca --- /dev/null +++ b/test/listeners/workspaceModifiedListener.test.ts @@ -0,0 +1,269 @@ +/* eslint-disable unused-imports/no-unused-imports */ +import * as vscode from 'vscode'; +import path from 'path'; + +import { envConfig } from '../../src/utils/envConfig'; +import { WorkspaceModifiedListener } from '../../src/listeners/workspaceModifiedListener'; +import { SmellsCacheManager } from '../../src/context/SmellsCacheManager'; +import { SmellsViewProvider } from '../../src/providers/SmellsViewProvider'; +import { MetricsViewProvider } from '../../src/providers/MetricsViewProvider'; +import { ecoOutput } from '../../src/extension'; +import { detectSmellsFile } from '../../src/commands/detection/detectSmells'; + +// Mock dependencies +jest.mock('path', () => ({ + basename: jest.fn((path) => path), +})); +jest.mock('../../src/extension'); +jest.mock('../../src/commands/detection/detectSmells'); +jest.mock('../../src/utils/envConfig'); + +describe('WorkspaceModifiedListener', () => { + let mockContext: vscode.ExtensionContext; + let mockSmellsCacheManager: jest.Mocked; + let mockSmellsViewProvider: jest.Mocked; + let mockMetricsViewProvider: jest.Mocked; + let listener: WorkspaceModifiedListener; + + beforeEach(() => { + jest.clearAllMocks(); + + mockContext = { + workspaceState: { + get: jest.fn(), + }, + } as unknown as vscode.ExtensionContext; + + mockSmellsCacheManager = { + hasFileInCache: jest.fn(), + hasCachedSmells: jest.fn(), + clearCachedSmellsForFile: jest.fn(), + clearCachedSmellsByPath: jest.fn(), + getAllFilePaths: jest.fn(() => []), + } as unknown as jest.Mocked; + + mockSmellsViewProvider = { + setStatus: jest.fn(), + removeFile: jest.fn(), + refresh: jest.fn(), + } as unknown as jest.Mocked; + + mockMetricsViewProvider = { + refresh: jest.fn(), + } as unknown as jest.Mocked; + }); + + describe('Initialization', () => { + it('should initialize without workspace path', () => { + (mockContext.workspaceState.get as jest.Mock).mockReturnValue(undefined); + new WorkspaceModifiedListener( + mockContext, + mockSmellsCacheManager, + mockSmellsViewProvider, + mockMetricsViewProvider, + ); + expect(ecoOutput.trace).toHaveBeenCalledWith( + '[WorkspaceListener] No workspace configured - skipping file watcher', + ); + }); + + it('should initialize with workspace path', () => { + (mockContext.workspaceState.get as jest.Mock).mockReturnValue('/project/path'); + listener = new WorkspaceModifiedListener( + mockContext, + mockSmellsCacheManager, + mockSmellsViewProvider, + mockMetricsViewProvider, + ); + + console.log((ecoOutput.trace as jest.Mock).mock); + expect(vscode.workspace.createFileSystemWatcher).toHaveBeenCalled(); + expect(ecoOutput.trace).toHaveBeenCalledWith( + '[WorkspaceListener] Watching Python files in /project/path', + ); + }); + }); + + describe('File Change Handling', () => { + beforeEach(() => { + (mockContext.workspaceState.get as jest.Mock).mockReturnValue('/project/path'); + listener = new WorkspaceModifiedListener( + mockContext, + mockSmellsCacheManager, + mockSmellsViewProvider, + mockMetricsViewProvider, + ); + }); + + it('should handle file change with existing cache', async () => { + const filePath = '/project/path/file.py'; + (mockSmellsCacheManager.hasFileInCache as jest.Mock).mockReturnValue(true); + + await listener['handleFileChange'](filePath); + + expect(mockSmellsCacheManager.clearCachedSmellsForFile).toHaveBeenCalledWith( + filePath, + ); + expect(mockSmellsViewProvider.setStatus).toHaveBeenCalledWith( + filePath, + 'outdated', + ); + expect(vscode.window.showInformationMessage).toHaveBeenCalled(); + expect(mockSmellsViewProvider.refresh).toHaveBeenCalled(); + }); + + it('should skip file change without cache', async () => { + const filePath = '/project/path/file.py'; + (mockSmellsCacheManager.hasFileInCache as jest.Mock).mockReturnValue(false); + + await listener['handleFileChange'](filePath); + + expect(mockSmellsCacheManager.clearCachedSmellsForFile).not.toHaveBeenCalled(); + expect(ecoOutput.trace).toHaveBeenCalledWith( + '[WorkspaceListener] No cache to invalidate for /project/path/file.py', + ); + }); + + it('should handle file change errors', async () => { + const filePath = '/project/path/file.py'; + (mockSmellsCacheManager.hasFileInCache as jest.Mock).mockReturnValue(true); + ( + mockSmellsCacheManager.clearCachedSmellsForFile as jest.Mock + ).mockRejectedValue(new Error('Cache error')); + + await listener['handleFileChange'](filePath); + + expect(ecoOutput.error).toHaveBeenCalledWith( + expect.stringContaining('Error handling file change: Cache error'), + ); + }); + }); + + describe('File Deletion Handling', () => { + beforeEach(() => { + (mockContext.workspaceState.get as jest.Mock).mockReturnValue('/project/path'); + listener = new WorkspaceModifiedListener( + mockContext, + mockSmellsCacheManager, + mockSmellsViewProvider, + mockMetricsViewProvider, + ); + }); + + it('should handle file deletion with cache', async () => { + const filePath = '/project/path/file.py'; + (mockSmellsCacheManager.hasCachedSmells as jest.Mock).mockReturnValue(true); + (mockSmellsViewProvider.removeFile as jest.Mock).mockReturnValue(true); + + await listener['handleFileDeletion'](filePath); + + expect(mockSmellsCacheManager.clearCachedSmellsByPath).toHaveBeenCalledWith( + filePath, + ); + expect(mockSmellsViewProvider.removeFile).toHaveBeenCalledWith(filePath); + // expect(vscode.window.showInformationMessage).toHaveBeenCalled(); + expect(mockSmellsViewProvider.refresh).toHaveBeenCalled(); + }); + + it('should handle file deletion without cache', async () => { + const filePath = '/project/path/file.py'; + (mockSmellsCacheManager.hasCachedSmells as jest.Mock).mockReturnValue(false); + (mockSmellsViewProvider.removeFile as jest.Mock).mockReturnValue(false); + + await listener['handleFileDeletion'](filePath); + + expect(mockSmellsCacheManager.clearCachedSmellsByPath).not.toHaveBeenCalled(); + expect(vscode.window.showInformationMessage).not.toHaveBeenCalled(); + }); + + it('should handle deletion errors', async () => { + const filePath = '/project/path/file.py'; + (mockSmellsCacheManager.hasCachedSmells as jest.Mock).mockReturnValue(true); + ( + mockSmellsCacheManager.clearCachedSmellsByPath as jest.Mock + ).mockRejectedValue(new Error('Deletion error')); + + await listener['handleFileDeletion'](filePath); + + expect(ecoOutput.error).toHaveBeenCalledWith( + expect.stringContaining('Error clearing cache: Deletion error'), + ); + }); + }); + + describe('Save Listener', () => { + it('should trigger smell detection on Python file save when enabled', () => { + (mockContext.workspaceState.get as jest.Mock).mockReturnValue('/project/path'); + ( + require('../../src/extension').isSmellLintingEnabled as jest.Mock + ).mockReturnValue(true); + + listener = new WorkspaceModifiedListener( + mockContext, + mockSmellsCacheManager, + mockSmellsViewProvider, + mockMetricsViewProvider, + ); + + // Trigger save event + const onDidSave = (vscode.workspace.onDidSaveTextDocument as jest.Mock).mock + .calls[0][0]; + const mockDocument = { + languageId: 'python', + uri: { fsPath: '/project/path/file.py' }, + }; + onDidSave(mockDocument); + + expect(detectSmellsFile).toHaveBeenCalledWith( + '/project/path/file.py', + mockSmellsViewProvider, + mockSmellsCacheManager, + ); + expect(ecoOutput.info).toHaveBeenCalledWith( + '[WorkspaceListener] Smell linting is ON — auto-detecting smells for /project/path/file.py', + ); + }); + + it('should skip non-Python files on save', () => { + (mockContext.workspaceState.get as jest.Mock).mockReturnValue('/project/path'); + + listener = new WorkspaceModifiedListener( + mockContext, + mockSmellsCacheManager, + mockSmellsViewProvider, + mockMetricsViewProvider, + ); + + // Trigger save event + const onDidSave = (vscode.workspace.onDidSaveTextDocument as jest.Mock).mock + .calls[0][0]; + const mockDocument = { + languageId: 'javascript', + uri: { fsPath: '/project/path/file.js' }, + }; + onDidSave(mockDocument); + + expect(detectSmellsFile).not.toHaveBeenCalled(); + }); + }); + + describe('Disposal', () => { + it('should clean up resources on dispose', () => { + (mockContext.workspaceState.get as jest.Mock).mockReturnValue('/project/path'); + listener = new WorkspaceModifiedListener( + mockContext, + mockSmellsCacheManager, + mockSmellsViewProvider, + mockMetricsViewProvider, + ); + + listener.dispose(); + + expect(listener['fileWatcher']?.dispose).toHaveBeenCalled(); + expect(listener['saveListener']?.dispose).toHaveBeenCalled(); + expect(ecoOutput.trace).toHaveBeenCalledWith( + '[WorkspaceListener] Disposed all listeners', + ); + }); + }); +}); diff --git a/test/mocks/context-mock.ts b/test/mocks/context-mock.ts new file mode 100644 index 0000000..6cd7580 --- /dev/null +++ b/test/mocks/context-mock.ts @@ -0,0 +1,31 @@ +// test/mocks/contextManager-mock.ts +interface ContextStorage { + globalState: Record; + workspaceState: Record; +} + +const contextStorage: ContextStorage = { + globalState: {}, + workspaceState: {}, +}; + +const mockExtensionContext = { + globalState: { + get: jest.fn((key: string, defaultVal?: any) => { + return contextStorage.globalState[key] ?? defaultVal; + }), + update: jest.fn(async (key: string, value: any) => { + contextStorage.globalState[key] = value; + }), + } as any, + workspaceState: { + get: jest.fn((key: string, defaultVal?: any) => { + return contextStorage.workspaceState[key] ?? defaultVal; + }), + update: jest.fn(async (key: string, value: any) => { + contextStorage.workspaceState[key] = value; + }), + } as any, +}; + +export default mockExtensionContext; diff --git a/test/mocks/env-config-mock.ts b/test/mocks/env-config-mock.ts new file mode 100644 index 0000000..e98c4df --- /dev/null +++ b/test/mocks/env-config-mock.ts @@ -0,0 +1,12 @@ +import { EnvConfig } from '../../src/utils/envConfig'; + +const mockEnvConfig: EnvConfig = { + SERVER_URL: 'localhost:8000', + SMELL_CACHE_KEY: 'value2', + HASH_PATH_MAP_KEY: 'value3', + WORKSPACE_METRICS_DATA: 'value4', + WORKSPACE_CONFIGURED_PATH: 'value5', + UNFINISHED_REFACTORING: 'value6', +}; + +export default mockEnvConfig; diff --git a/test/mocks/vscode-mock.ts b/test/mocks/vscode-mock.ts new file mode 100644 index 0000000..7e97708 --- /dev/null +++ b/test/mocks/vscode-mock.ts @@ -0,0 +1,373 @@ +// test/mocks/vscode-mock.ts +interface Config { + configGet: any; + filePath: any; + docText: any; + workspacePath: any; +} + +// Configuration object to dynamically change values during tests +export const config: Config = { + configGet: { smell1: true, smell2: true }, + filePath: 'fake.py', + docText: 'Mock document text', + workspacePath: '/workspace/path', +}; + +export const TextDocument = { + getText: jest.fn(() => config.docText), + fileName: config.filePath, + languageId: 'python', + lineAt: jest.fn((_line: number) => { + return { + text: 'Mock line text', + }; + }), + lineCount: 10, + uri: { + scheme: 'file', + fsPath: config.filePath, + }, +}; + +// Mock for `vscode.TextEditor` +export const TextEditor = { + document: TextDocument, + selection: { + start: { line: 0, character: 0 }, + end: { line: 0, character: 0 }, + isSingleLine: true, + }, + setDecorations: jest.fn(), + revealRange: jest.fn(), +}; + +export interface TextEditorDecorationType { + dispose: jest.Mock; +} + +const textEditorDecorationType: TextEditorDecorationType = { + dispose: jest.fn(), +}; + +interface Window { + showInformationMessage: jest.Mock; + showErrorMessage: jest.Mock; + showWarningMessage: jest.Mock; + createTextEditorDecorationType: jest.Mock; + createOutputChannel: jest.Mock; + activeTextEditor: any; + visibleTextEditors: any[]; + withProgress: jest.Mock; + showInputBox: jest.Mock; + showQuickPick: jest.Mock; +} + +export const window: Window = { + showInformationMessage: jest.fn(async (message: string, options?: any) => { + return options?.modal ? 'Confirm' : message; + }), + showErrorMessage: jest.fn(async (message: string) => { + return message; + }), + showWarningMessage: jest.fn(async (message: string, options?: any) => { + return options?.modal ? 'Confirm' : message; + }), + activeTextEditor: TextEditor, + visibleTextEditors: [], + createTextEditorDecorationType: jest.fn((_options: any) => { + return textEditorDecorationType; + }), + createOutputChannel: jest.fn(() => ({ + appendLine: jest.fn(), + show: jest.fn(), + clear: jest.fn(), + log: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), + })), + withProgress: jest.fn((options, task) => { + return task({ + report: jest.fn(), + }); + }), + showInputBox: jest.fn((val) => {}), + showQuickPick: jest.fn(), +}; + +export enum FileType { + Directory = 1, + File = 2, +} + +interface Workspace { + getConfiguration: jest.Mock; + createFileSystemWatcher: jest.Mock; + onDidSaveTextDocument: jest.Mock; + findFiles: jest.Mock; + fs: { + readFile: jest.Mock; + writeFile: jest.Mock; + stat: jest.Mock; + }; +} + +export const workspace: Workspace = { + getConfiguration: jest.fn((_section?: string) => ({ + get: jest.fn(() => config.configGet), + update: jest.fn(), + })), + createFileSystemWatcher: jest.fn( + () => + ({ + onDidCreate: jest.fn(), + onDidDelete: jest.fn(), + dispose: jest.fn(), + }) as unknown, + ), + onDidSaveTextDocument: jest.fn( + () => + ({ + dispose: jest.fn(), + }) as unknown, + ), + findFiles: jest.fn(), + fs: { + readFile: jest.fn(), + writeFile: jest.fn(), + stat: jest.fn(), + }, +}; + +interface MockCommand { + title: string; + command: string; + arguments?: any[]; + tooltip?: string; +} + +export const Command = jest + .fn() + .mockImplementation((title: string, command: string, ...args: any[]) => { + return { + title, + command, + arguments: args, + tooltip: title, + }; + }) as jest.Mock & { + prototype: MockCommand; +}; + +export const OverviewRulerLane = { + Right: 'Right', +}; + +export const Range = class MockRange { + constructor( + public startLine: number, + public startCharacter: number, + public endLine: number, + public endCharacter: number, + ) {} +}; + +export const languages = { + registerHoverProvider: jest.fn(() => ({ + dispose: jest.fn(), + })), + registerCodeActionsProvider: jest.fn(), +}; + +export enum ProgressLocation { + SourceControl = 1, + Window = 10, + Notification = 15, +} + +// ProgressOptions interface +interface ProgressOptions { + location: ProgressLocation | { viewId: string }; + title?: string; + cancellable?: boolean; +} + +// Progress mock +interface Progress { + report(value: T): void; +} + +// Window.withProgress mock implementation +window.withProgress = jest.fn( + ( + options: ProgressOptions, + task: ( + progress: Progress<{ message?: string; increment?: number }>, + ) => Promise, + ) => { + const progress = { + report: jest.fn(), + }; + return task(progress); + }, +); + +export const commands = { + registerCommand: jest.fn((command: string, func: Function) => ({ + dispose: jest.fn(), + })), + executeCommand: jest.fn((command: string) => { + if (command === 'setContext') { + return Promise.resolve(); + } + return Promise.resolve(); + }), +}; + +export const Uri = { + file: jest.fn((path: string) => ({ + scheme: 'file', + path, + fsPath: path, + toString: (): string => path, + })), + joinPath: jest.fn((start: string, end: string) => { + const newPath = start + end; + return { + scheme: 'file', + path: newPath, + fsPath: newPath, + toString: (): string => newPath, + }; + }), + parse: jest.fn(), +}; + +export const Position = class MockPosition { + constructor( + public line: number, + public character: number, + ) {} +}; + +interface MockMarkdownString { + appendMarkdown: jest.Mock; + value: string; + isTrusted: boolean; +} + +export class RelativePattern { + constructor( + public path: string, + pattern: string, + ) {} +} + +export const MarkdownString = jest.fn().mockImplementation(() => { + return { + appendMarkdown: jest.fn(function (this: any, value: string) { + this.value += value; + return this; + }), + value: '', + isTrusted: false, + }; +}) as jest.Mock & { + prototype: MockMarkdownString; +}; + +export class MockHover { + constructor(public contents: MockMarkdownString) {} +} + +export enum TreeItemCollapsibleState { + None = 0, + Collapsed = 1, + Expanded = 2, +} + +export class MockTreeItem { + constructor( + public label: string, + public collapsibleState?: TreeItemCollapsibleState, + public command?: MockCommand, + ) {} + + iconPath?: + | string + | typeof Uri + | { light: string | typeof Uri; dark: string | typeof Uri }; + description?: string; + tooltip?: string; + contextValue?: string; +} + +export const TreeItem = MockTreeItem; + +export const Hover = MockHover; + +export const ExtensionContext = { + subscriptions: [], + workspaceState: { + get: jest.fn((key: string) => { + if (key === 'workspaceConfiguredPath') { + return config.workspacePath; + } + return undefined; + }), + update: jest.fn(), + }, + globalState: { + get: jest.fn(), + update: jest.fn(), + }, +}; + +export interface Vscode { + window: Window; + workspace: Workspace; + TextDocument: typeof TextDocument; + TextEditor: typeof TextEditor; + TextEditorDecorationType: TextEditorDecorationType; + languages: typeof languages; + commands: typeof commands; + OverviewRulerLane: typeof OverviewRulerLane; + ProgressLocation: typeof ProgressLocation; + FileType: typeof FileType; + RelativePattern: typeof RelativePattern; + Range: typeof Range; + Position: typeof Position; + Hover: typeof Hover; + Command: typeof Command; + Uri: typeof Uri; + TreeItem: typeof TreeItem; + TreeItemCollapsibleState: typeof TreeItemCollapsibleState; + ExtensionContext: typeof ExtensionContext; +} + +const vscode: Vscode = { + window, + workspace, + TextDocument, + TextEditor, + TextEditorDecorationType: textEditorDecorationType, + languages, + commands, + OverviewRulerLane, + ProgressLocation, + FileType, + RelativePattern, + Range, + Position, + Hover, + Command, + Uri, + TreeItem, + TreeItemCollapsibleState, + ExtensionContext, +}; + +export default vscode; diff --git a/test/setup.ts b/test/setup.ts new file mode 100644 index 0000000..f201390 --- /dev/null +++ b/test/setup.ts @@ -0,0 +1,7 @@ +import mockEnvConfig from './mocks/env-config-mock'; + +jest.mock('vscode'); + +jest.mock('../src/utils/envConfig', () => ({ + envConfig: mockEnvConfig, +})); diff --git a/test/ui/fileHighlighter.test.ts b/test/ui/fileHighlighter.test.ts new file mode 100644 index 0000000..8fb7a67 --- /dev/null +++ b/test/ui/fileHighlighter.test.ts @@ -0,0 +1,295 @@ +// test/fileHighlighter.test.ts +import * as vscode from 'vscode'; +import { FileHighlighter } from '../../src/ui/fileHighlighter'; +import { SmellsCacheManager } from '../../src/context/SmellsCacheManager'; +import { ConfigManager } from '../../src/context/configManager'; +import * as smellsData from '../../src/utils/smellsData'; + +// Mock dependencies +jest.mock('vscode'); +jest.mock('../../src/context/SmellsCacheManager'); +jest.mock('../../src/context/configManager'); +jest.mock('../../src/utils/smellsData'); + +describe('FileHighlighter', () => { + let smellsCacheManager: { getCachedSmells: jest.Mock; onSmellsUpdated: jest.Mock }; + let fileHighlighter: FileHighlighter; + + beforeEach(() => { + jest.clearAllMocks(); + + // Setup mock instances + smellsCacheManager = { + getCachedSmells: jest.fn(), + onSmellsUpdated: jest.fn(), + }; + FileHighlighter['instance'] = undefined; + fileHighlighter = FileHighlighter.getInstance( + smellsCacheManager as unknown as SmellsCacheManager, + ); + + // Mock ConfigManager + (ConfigManager.get as jest.Mock).mockImplementation((key: string) => { + switch (key) { + case 'smellsColours': + return { smell1: 'rgba(255,0,0,0.5)', smell2: 'rgba(0,0,255,0.5)' }; + case 'useSingleColour': + return false; + case 'singleHighlightColour': + return 'rgba(255,204,0,0.5)'; + case 'highlightStyle': + return 'underline'; + default: + return undefined; + } + }); + + // Mock createTextEditorDecorationType + (vscode.window.createTextEditorDecorationType as jest.Mock).mockImplementation( + () => ({ + dispose: jest.fn(), + }), + ); + }); + + afterEach(() => { + jest.restoreAllMocks(); // Cleans up all spy mocks + (vscode.window.createTextEditorDecorationType as jest.Mock).mockClear(); + }); + + describe('getInstance', () => { + it('should return singleton instance', () => { + const instance1 = FileHighlighter.getInstance( + smellsCacheManager as unknown as SmellsCacheManager, + ); + const instance2 = FileHighlighter.getInstance( + smellsCacheManager as unknown as SmellsCacheManager, + ); + expect(instance1).toBe(instance2); + }); + }); + + describe('updateHighlightsForVisibleEditors', () => { + it('should call highlightSmells for each visible Python editor', () => { + // Mock highlightSmells to track calls + const highlightSpy = jest.spyOn(fileHighlighter, 'highlightSmells'); + + // Create a non-Python editor + const nonPythonEditor = { + document: { + fileName: '/path/to/file.js', + uri: { fsPath: '/path/to/file.js' }, + }, + } as unknown as vscode.TextEditor; + + vscode.window.visibleTextEditors = [ + nonPythonEditor, + vscode.window.activeTextEditor!, + ]; + + fileHighlighter.updateHighlightsForVisibleEditors(); + + // Verify highlightSmells was called exactly once (for the Python editor) + expect(highlightSpy).toHaveBeenCalledTimes(1); + + // Clean up spy + highlightSpy.mockRestore(); + }); + }); + + describe('updateHighlightsForFile', () => { + it('should call highlightSmells when matching Python file is visible', () => { + const highlightSpy = jest.spyOn(fileHighlighter, 'highlightSmells'); + + vscode.window.visibleTextEditors = [vscode.window.activeTextEditor!]; + + fileHighlighter['updateHighlightsForFile']('fake.py'); + + expect(highlightSpy).toHaveBeenCalledTimes(1); + highlightSpy.mockRestore(); + }); + + it('should not call highlightSmells for non-matching files', () => { + const highlightSpy = jest.spyOn(fileHighlighter, 'highlightSmells'); + + fileHighlighter['updateHighlightsForFile']('/path/to/other.py'); + + expect(highlightSpy).not.toHaveBeenCalled(); + highlightSpy.mockRestore(); + }); + + it('should not call highlightSmells for non-Python files', () => { + const highlightSpy = jest.spyOn(fileHighlighter, 'highlightSmells'); + + fileHighlighter['updateHighlightsForFile']('/path/to/file.js'); + + expect(highlightSpy).not.toHaveBeenCalled(); + highlightSpy.mockRestore(); + }); + }); + + describe('highlightSmells', () => { + const mockEditor = vscode.window.activeTextEditor; + it('should highlight smells when cache has data', () => { + const mockSmells = [ + { + symbol: 'smell1', + occurences: [{ line: 1 }, { line: 2 }], + }, + { + symbol: 'smell2', + occurences: [{ line: 3 }], + }, + ] as unknown as Smell[]; + + jest.spyOn(smellsData, 'getEnabledSmells').mockReturnValueOnce({ + smell1: {} as any, + smell2: {} as any, + }); + + (smellsCacheManager.getCachedSmells as jest.Mock).mockReturnValueOnce( + mockSmells, + ); + + console.log( + 'Mock getCachedSmells implementation:', + smellsCacheManager.getCachedSmells.mock.results, + ); + + const editor = vscode.window.activeTextEditor; + + fileHighlighter.highlightSmells(editor!); + + expect(vscode.window.createTextEditorDecorationType).toHaveBeenCalledTimes(2); + expect(editor!.setDecorations).toHaveBeenCalledTimes(2); + }); + + it('should not highlight when cache has no data', () => { + smellsCacheManager.getCachedSmells.mockReturnValueOnce(undefined); + fileHighlighter.highlightSmells(mockEditor!); + expect(mockEditor!.setDecorations).not.toHaveBeenCalled(); + }); + + it('should only highlight enabled smells', () => { + jest.spyOn(smellsData, 'getEnabledSmells').mockReturnValueOnce({ + smell1: {} as any, + }); + + const mockSmells = [ + { + symbol: 'smell1', + occurences: [{ line: 1 }], + }, + { + symbol: 'smell2', + occurences: [{ line: 2 }], + }, + ]; + + smellsCacheManager.getCachedSmells.mockReturnValueOnce(mockSmells); + + fileHighlighter.highlightSmells(mockEditor!); + + expect( + (mockEditor?.setDecorations as jest.Mock).mock.calls[0][1], + ).toHaveLength(1); + }); + + it('should skip invalid line numbers', () => { + const mockSmells = [ + { + symbol: 'smell1', + occurences: [{ line: 100 }], // Invalid line number + }, + ]; + + jest.spyOn(smellsData, 'getEnabledSmells').mockReturnValueOnce({ + smell1: {} as any, + smell2: {} as any, + }); + + smellsCacheManager.getCachedSmells.mockReturnValueOnce(mockSmells); + + fileHighlighter.highlightSmells(mockEditor!); + + expect(mockEditor?.setDecorations).toHaveBeenCalledWith(expect.anything(), []); + }); + }); + + describe('resetHighlights', () => { + it('should dispose all decorations', () => { + const mockEditor = vscode.window.activeTextEditor; + const mockDecoration = { dispose: jest.fn() }; + ( + vscode.window.createTextEditorDecorationType as jest.Mock + ).mockReturnValueOnce(mockDecoration); + + jest.spyOn(smellsData, 'getEnabledSmells').mockReturnValueOnce({ + smell1: {} as any, + smell2: {} as any, + }); + + const mockSmells = [{ symbol: 'smell1', occurences: [{ line: 1 }] }]; + smellsCacheManager.getCachedSmells.mockReturnValueOnce(mockSmells); + + fileHighlighter.highlightSmells(mockEditor!); + fileHighlighter.resetHighlights(); + + expect(mockDecoration.dispose).toHaveBeenCalled(); + expect(fileHighlighter['decorations']).toHaveLength(0); + }); + }); + + describe('getDecoration', () => { + it('should create underline decoration', () => { + (ConfigManager.get as jest.Mock).mockImplementation((key: string) => + key === 'highlightStyle' ? 'underline' : undefined, + ); + + fileHighlighter['getDecoration']('rgba(255,0,0,0.5)', 'underline'); + expect(vscode.window.createTextEditorDecorationType).toHaveBeenCalledWith({ + textDecoration: 'wavy rgba(255,0,0,0.5) underline 1px', + }); + }); + + it('should create flashlight decoration', () => { + (ConfigManager.get as jest.Mock).mockImplementation((key: string) => + key === 'highlightStyle' ? 'flashlight' : undefined, + ); + + fileHighlighter['getDecoration']('rgba(255,0,0,0.5)', 'flashlight'); + expect(vscode.window.createTextEditorDecorationType).toHaveBeenCalledWith({ + isWholeLine: true, + backgroundColor: 'rgba(255,0,0,0.5)', + }); + }); + + it('should create border-arrow decoration', () => { + (ConfigManager.get as jest.Mock).mockImplementation((key: string) => + key === 'highlightStyle' ? 'border-arrow' : undefined, + ); + + fileHighlighter['getDecoration']('rgba(255,0,0,0.5)', 'border-arrow'); + expect(vscode.window.createTextEditorDecorationType).toHaveBeenCalledWith({ + borderWidth: '1px 2px 1px 0', + borderStyle: 'solid', + borderColor: 'rgba(255,0,0,0.5)', + after: { + contentText: '▶', + margin: '0 0 0 5px', + color: 'rgba(255,0,0,0.5)', + fontWeight: 'bold', + }, + overviewRulerColor: 'rgba(255,0,0,0.5)', + overviewRulerLane: vscode.OverviewRulerLane.Right, + }); + }); + + it('should default to underline for unknown styles', () => { + fileHighlighter['getDecoration']('rgba(255,0,0,0.5)', 'unknown'); + expect(vscode.window.createTextEditorDecorationType).toHaveBeenCalledWith({ + textDecoration: 'wavy rgba(255,0,0,0.5) underline 1px', + }); + }); + }); +}); diff --git a/test/ui/hoverManager.test.ts b/test/ui/hoverManager.test.ts new file mode 100644 index 0000000..57d22a9 --- /dev/null +++ b/test/ui/hoverManager.test.ts @@ -0,0 +1,190 @@ +import * as vscode from 'vscode'; +import { HoverManager } from '../../src/ui/hoverManager'; +import { SmellsCacheManager } from '../../src/context/SmellsCacheManager'; + +// Create a simple mock Uri implementation +const mockUri = (path: string): vscode.Uri => ({ + scheme: 'file', + authority: '', + path, + fsPath: path, + query: '', + fragment: '', + with: jest.fn(), + toString: jest.fn(() => path), + toJSON: jest.fn(() => ({ path })), +}); + +// Mock the vscode module with all required components +jest.mock('vscode', () => { + const actualVscode = jest.requireActual('vscode'); + + // Mock MarkdownString implementation + const mockMarkdownString = { + isTrusted: true, + supportHtml: true, + supportThemeIcons: true, + appendMarkdown: jest.fn(), + }; + + return { + ...actualVscode, + languages: { + registerHoverProvider: jest.fn(), + }, + MarkdownString: jest.fn(() => mockMarkdownString), + Hover: jest.fn(), + Position: jest.fn(), + Uri: { + file: jest.fn((path) => mockUri(path)), + parse: jest.fn((path) => mockUri(path)), + }, + }; +}); + +describe('HoverManager', () => { + let hoverManager: HoverManager; + let mockSmellsCacheManager: jest.Mocked; + let mockContext: vscode.ExtensionContext; + let mockDocument: vscode.TextDocument; + let mockPosition: vscode.Position; + + const createMockSmell = (messageId: string, line: number) => ({ + type: 'performance', + symbol: 'test-smell', + message: 'Test smell message', + messageId, + confidence: 'HIGH', + path: '/test/file.py', + module: 'test', + occurences: [ + { + line, + column: 1, + endLine: line, + endColumn: 10, + }, + ], + additionalInfo: {}, + }); + + beforeEach(() => { + jest.clearAllMocks(); + + mockSmellsCacheManager = { + getCachedSmells: jest.fn(), + } as unknown as jest.Mocked; + + mockContext = { + subscriptions: [], + } as unknown as vscode.ExtensionContext; + + mockDocument = { + uri: vscode.Uri.file('/test/file.py'), + fileName: '/test/file.py', + lineAt: jest.fn(), + } as unknown as vscode.TextDocument; + + mockPosition = { + line: 5, + character: 0, + } as unknown as vscode.Position; + + hoverManager = new HoverManager(mockSmellsCacheManager); + }); + + describe('register', () => { + it('should register hover provider for Python files', () => { + hoverManager.register(mockContext); + + expect(vscode.languages.registerHoverProvider).toHaveBeenCalledWith( + { language: 'python', scheme: 'file' }, + hoverManager, + ); + expect(mockContext.subscriptions).toHaveLength(1); + }); + }); + + describe('provideHover', () => { + it('should return undefined for non-Python files', () => { + const jsDocument = { + uri: vscode.Uri.file('/test/file.js'), + fileName: '/test/file.js', + } as vscode.TextDocument; + + const result = hoverManager.provideHover( + jsDocument, + mockPosition, + {} as vscode.CancellationToken, + ); + expect(result).toBeUndefined(); + }); + + it('should return undefined when no smells are cached', () => { + mockSmellsCacheManager.getCachedSmells.mockReturnValue(undefined); + const result = hoverManager.provideHover( + mockDocument, + mockPosition, + {} as vscode.CancellationToken, + ); + expect(result).toBeUndefined(); + }); + + it('should return undefined when no smells at line', () => { + mockSmellsCacheManager.getCachedSmells.mockReturnValue([ + createMockSmell('test-smell', 10), // Different line + ]); + const result = hoverManager.provideHover( + mockDocument, + mockPosition, + {} as vscode.CancellationToken, + ); + expect(result).toBeUndefined(); + }); + + it('should create hover for single smell at line', () => { + const mockSmell = createMockSmell('test-smell', 6); // line + 1 + mockSmellsCacheManager.getCachedSmells.mockReturnValue([mockSmell]); + + const result = hoverManager.provideHover( + mockDocument, + mockPosition, + {} as vscode.CancellationToken, + ); + + expect(vscode.MarkdownString).toHaveBeenCalled(); + expect(vscode.Hover).toHaveBeenCalled(); + + // Get the mock MarkdownString instance + const markdownInstance = (vscode.MarkdownString as jest.Mock).mock.results[0] + .value; + expect(markdownInstance.appendMarkdown).toHaveBeenCalledWith( + expect.stringContaining('Test smell message'), + ); + expect(markdownInstance.appendMarkdown).toHaveBeenCalledWith( + expect.stringContaining('command:ecooptimizer.refactorSmell'), + ); + }); + + it('should escape special characters in messages', () => { + const mockSmell = { + ...createMockSmell('test-smell', 6), + message: 'Message with *stars* and _underscores_', + messageId: 'id_with*stars*', + }; + mockSmellsCacheManager.getCachedSmells.mockReturnValue([mockSmell]); + + hoverManager.provideHover( + mockDocument, + mockPosition, + {} as vscode.CancellationToken, + ); + + const markdownInstance = (vscode.MarkdownString as jest.Mock).mock.results[0] + .value; + expect(markdownInstance.appendMarkdown).toHaveBeenCalledWith( + expect.stringContaining('Message with \\*stars\\* and \\_underscores\\_'), + ); + }); + }); +}); diff --git a/test/ui/lineSelection.test.ts b/test/ui/lineSelection.test.ts new file mode 100644 index 0000000..37ba48f --- /dev/null +++ b/test/ui/lineSelection.test.ts @@ -0,0 +1,221 @@ +import * as vscode from 'vscode'; +import { LineSelectionManager } from '../../src/ui/lineSelectionManager'; +import { SmellsCacheManager } from '../../src/context/SmellsCacheManager'; + +jest.mock('vscode', () => { + const actualVscode = jest.requireActual('vscode'); + return { + ...actualVscode, + window: { + ...actualVscode.window, + createTextEditorDecorationType: jest.fn(), + activeTextEditor: undefined, + }, + ThemeColor: jest.fn((colorName: string) => ({ id: colorName })), + }; +}); + +describe('LineSelectionManager', () => { + let manager: LineSelectionManager; + let mockSmellsCacheManager: jest.Mocked; + let mockEditor: vscode.TextEditor; + let mockDocument: vscode.TextDocument; + let mockDecorationType: vscode.TextEditorDecorationType; + + // Helper function to create a mock smell + const createMockSmell = (symbol: string, line: number) => ({ + type: 'performance', + symbol, + message: 'Test smell', + messageId: 'test-smell', + confidence: 'HIGH', + path: '/test/file.js', + module: 'test', + occurences: [ + { + line, + column: 1, + endLine: line, + endColumn: 10, + }, + ], + additionalInfo: {}, + }); + + beforeEach(() => { + jest.clearAllMocks(); + + mockSmellsCacheManager = { + onSmellsUpdated: jest.fn(), + getCachedSmells: jest.fn(), + } as unknown as jest.Mocked; + + mockDocument = { + fileName: '/test/file.js', + lineAt: jest.fn().mockReturnValue({ + text: 'const test = true;', + trimEnd: jest.fn().mockReturnValue('const test = true;'), + }), + uri: { + fsPath: '/test/file.js', + }, + } as unknown as vscode.TextDocument; + + mockEditor = { + document: mockDocument, + selection: { + isSingleLine: true, + start: { line: 5 }, + }, + setDecorations: jest.fn(), + } as unknown as vscode.TextEditor; + + mockDecorationType = { + dispose: jest.fn(), + } as unknown as vscode.TextEditorDecorationType; + + (vscode.window.createTextEditorDecorationType as jest.Mock).mockReturnValue( + mockDecorationType, + ); + + (vscode.window.activeTextEditor as unknown) = mockEditor; + + manager = new LineSelectionManager(mockSmellsCacheManager); + }); + + describe('constructor', () => { + it('should initialize with empty decoration and null lastDecoratedLine', () => { + expect((manager as any).decoration).toBeNull(); + expect((manager as any).lastDecoratedLine).toBeNull(); + }); + + it('should register smellsUpdated callback', () => { + expect(mockSmellsCacheManager.onSmellsUpdated).toHaveBeenCalled(); + }); + }); + + describe('removeLastComment', () => { + it('should dispose decoration if it exists', () => { + (manager as any).decoration = mockDecorationType; + (manager as any).lastDecoratedLine = 5; + + manager.removeLastComment(); + + expect(mockDecorationType.dispose).toHaveBeenCalled(); + expect((manager as any).decoration).toBeNull(); + expect((manager as any).lastDecoratedLine).toBeNull(); + }); + + it('should do nothing if no decoration exists', () => { + manager.removeLastComment(); + expect(mockDecorationType.dispose).not.toHaveBeenCalled(); + }); + }); + + describe('commentLine', () => { + it('should do nothing if no editor is provided', () => { + manager.commentLine(null as any); + expect(vscode.window.createTextEditorDecorationType).not.toHaveBeenCalled(); + }); + + it('should do nothing if selection is multi-line', () => { + (mockEditor.selection as any).isSingleLine = false; + manager.commentLine(mockEditor); + expect(vscode.window.createTextEditorDecorationType).not.toHaveBeenCalled(); + }); + + it('should remove last comment if no smells are cached', () => { + mockSmellsCacheManager.getCachedSmells.mockReturnValue(undefined); + const removeSpy = jest.spyOn(manager, 'removeLastComment'); + + manager.commentLine(mockEditor); + + expect(removeSpy).toHaveBeenCalled(); + expect(vscode.window.createTextEditorDecorationType).not.toHaveBeenCalled(); + }); + + it('should do nothing if no smells exist at selected line', () => { + mockSmellsCacheManager.getCachedSmells.mockReturnValue([ + createMockSmell('LongMethod', 10), // Different line + ]); + + manager.commentLine(mockEditor); + + expect(vscode.window.createTextEditorDecorationType).not.toHaveBeenCalled(); + }); + + it('should create decoration for single smell at line', () => { + mockSmellsCacheManager.getCachedSmells.mockReturnValue([ + createMockSmell('LongMethod', 6), // line + 1 + ]); + + manager.commentLine(mockEditor); + + expect(vscode.window.createTextEditorDecorationType).toHaveBeenCalled(); + expect(mockEditor.setDecorations).toHaveBeenCalledWith( + mockDecorationType, + expect.any(Array), + ); + expect((manager as any).lastDecoratedLine).toBe(5); + + const decorationConfig = ( + vscode.window.createTextEditorDecorationType as jest.Mock + ).mock.calls[0][0]; + expect(decorationConfig.after.contentText).toBe('🍂 Smell: LongMethod'); + }); + + it('should create decoration with count for multiple smells at line', () => { + mockSmellsCacheManager.getCachedSmells.mockReturnValue([ + createMockSmell('LongMethod', 6), + createMockSmell('ComplexCondition', 6), + ]); + + manager.commentLine(mockEditor); + + const decorationConfig = ( + vscode.window.createTextEditorDecorationType as jest.Mock + ).mock.calls[0][0]; + expect(decorationConfig.after.contentText).toContain( + '🍂 Smell: LongMethod | (+1)', + ); + }); + + it('should not create decoration if same line is already decorated', () => { + (manager as any).lastDecoratedLine = 5; + mockSmellsCacheManager.getCachedSmells.mockReturnValue([ + createMockSmell('LongMethod', 6), + ]); + + manager.commentLine(mockEditor); + + expect(vscode.window.createTextEditorDecorationType).not.toHaveBeenCalled(); + }); + }); + + describe('smellsUpdated callback', () => { + let smellsUpdatedCallback: (targetFilePath: string) => void; + + beforeEach(() => { + smellsUpdatedCallback = (mockSmellsCacheManager.onSmellsUpdated as jest.Mock) + .mock.calls[0][0]; + }); + + it('should remove comment when cache is cleared for all files', () => { + const removeSpy = jest.spyOn(manager, 'removeLastComment'); + smellsUpdatedCallback('all'); + expect(removeSpy).toHaveBeenCalled(); + }); + + it('should remove comment when cache is cleared for current file', () => { + const removeSpy = jest.spyOn(manager, 'removeLastComment'); + smellsUpdatedCallback('/test/file.js'); + expect(removeSpy).toHaveBeenCalled(); + }); + + it('should not remove comment when cache is cleared for different file', () => { + const removeSpy = jest.spyOn(manager, 'removeLastComment'); + smellsUpdatedCallback('/other/file.js'); + expect(removeSpy).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/test/utils/initializeStatusesFromCache.test.ts b/test/utils/initializeStatusesFromCache.test.ts new file mode 100644 index 0000000..89b1ec6 --- /dev/null +++ b/test/utils/initializeStatusesFromCache.test.ts @@ -0,0 +1,167 @@ +// test/cacheInitialization.test.ts +import * as vscode from 'vscode'; +import * as fs from 'fs/promises'; +import { initializeStatusesFromCache } from '../../src/utils/initializeStatusesFromCache'; +import { SmellsViewProvider } from '../../src/providers/SmellsViewProvider'; +import { SmellsCacheManager } from '../../src/context/SmellsCacheManager'; +import { ecoOutput } from '../../src/extension'; +import { envConfig } from '../../src/utils/envConfig'; + +// Mock the external dependencies +jest.mock('fs/promises'); +jest.mock('../../src/extension'); +jest.mock('../../src/utils/envConfig'); +jest.mock('../../src/providers/SmellsViewProvider'); +jest.mock('../../src/context/SmellsCacheManager'); + +describe('initializeStatusesFromCache', () => { + let context: vscode.ExtensionContext; + let smellsViewProvider: SmellsViewProvider; + let smellsCacheManager: SmellsCacheManager; + const mockWorkspacePath = '/workspace/path'; + + beforeEach(() => { + // Reset all mocks before each test + jest.clearAllMocks(); + + // Setup mock instances + context = { + workspaceState: { + get: jest.fn(), + }, + } as unknown as vscode.ExtensionContext; + + smellsViewProvider = new SmellsViewProvider(context); + smellsCacheManager = new SmellsCacheManager(context); + + // Mock envConfig + (envConfig.WORKSPACE_CONFIGURED_PATH as any) = 'WORKSPACE_PATH_KEY'; + }); + + it('should skip initialization when no workspace path is configured', async () => { + (context.workspaceState.get as jest.Mock).mockReturnValue(undefined); + + await initializeStatusesFromCache( + context, + smellsCacheManager, + smellsViewProvider, + ); + + expect(ecoOutput.warn).toHaveBeenCalledWith( + expect.stringContaining('No configured workspace path found'), + ); + expect(smellsCacheManager.getAllFilePaths).not.toHaveBeenCalled(); + }); + + it('should remove files outside the workspace from cache', async () => { + const outsidePath = '/other/path/file.py'; + (context.workspaceState.get as jest.Mock).mockReturnValue(mockWorkspacePath); + (smellsCacheManager.getAllFilePaths as jest.Mock).mockReturnValue([outsidePath]); + + await initializeStatusesFromCache( + context, + smellsCacheManager, + smellsViewProvider, + ); + + expect(smellsCacheManager.clearCachedSmellsForFile).toHaveBeenCalledWith( + outsidePath, + ); + expect(ecoOutput.trace).toHaveBeenCalledWith( + expect.stringContaining('File outside workspace'), + ); + expect(ecoOutput.info).toHaveBeenCalledWith( + expect.stringContaining('1 files removed from cache'), + ); + }); + + it('should remove non-existent files from cache', async () => { + const filePath = `${mockWorkspacePath}/file.py`; + (context.workspaceState.get as jest.Mock).mockReturnValue(mockWorkspacePath); + (smellsCacheManager.getAllFilePaths as jest.Mock).mockReturnValue([filePath]); + (fs.access as jest.Mock).mockRejectedValue(new Error('File not found')); + + await initializeStatusesFromCache( + context, + smellsCacheManager, + smellsViewProvider, + ); + + expect(smellsCacheManager.clearCachedSmellsForFile).toHaveBeenCalledWith( + filePath, + ); + expect(ecoOutput.trace).toHaveBeenCalledWith( + expect.stringContaining('File not found - removing from cache'), + ); + }); + + it('should set status for files with smells', async () => { + const filePath = `${mockWorkspacePath}/file.py`; + const mockSmells = [{ id: 'smell1' }]; + (context.workspaceState.get as jest.Mock).mockReturnValue(mockWorkspacePath); + (smellsCacheManager.getAllFilePaths as jest.Mock).mockReturnValue([filePath]); + (fs.access as jest.Mock).mockResolvedValue(undefined); + (smellsCacheManager.getCachedSmells as jest.Mock).mockReturnValue(mockSmells); + + await initializeStatusesFromCache( + context, + smellsCacheManager, + smellsViewProvider, + ); + + expect(smellsViewProvider.setStatus).toHaveBeenCalledWith(filePath, 'passed'); + expect(smellsViewProvider.setSmells).toHaveBeenCalledWith(filePath, mockSmells); + expect(ecoOutput.trace).toHaveBeenCalledWith( + expect.stringContaining('Found 1 smells for file'), + ); + }); + + it('should set status for clean files', async () => { + const filePath = `${mockWorkspacePath}/clean.py`; + (context.workspaceState.get as jest.Mock).mockReturnValue(mockWorkspacePath); + (smellsCacheManager.getAllFilePaths as jest.Mock).mockReturnValue([filePath]); + (fs.access as jest.Mock).mockResolvedValue(undefined); + (smellsCacheManager.getCachedSmells as jest.Mock).mockReturnValue([]); + + await initializeStatusesFromCache( + context, + smellsCacheManager, + smellsViewProvider, + ); + + expect(smellsViewProvider.setStatus).toHaveBeenCalledWith(filePath, 'no_issues'); + expect(ecoOutput.trace).toHaveBeenCalledWith( + expect.stringContaining('File has no smells'), + ); + }); + + it('should log correct summary statistics', async () => { + const files = [ + `${mockWorkspacePath}/file1.py`, // with smells + `${mockWorkspacePath}/file2.py`, // clean + '/outside/path/file3.py', // outside workspace + `${mockWorkspacePath}/missing.py`, // will fail access + ]; + (context.workspaceState.get as jest.Mock).mockReturnValue(mockWorkspacePath); + (smellsCacheManager.getAllFilePaths as jest.Mock).mockReturnValue(files); + (fs.access as jest.Mock) + .mockResolvedValueOnce(undefined) // file1.py exists + .mockResolvedValueOnce(undefined) // file2.py exists + .mockRejectedValueOnce(new Error('File not found')); // missing.py doesn't exist + (smellsCacheManager.getCachedSmells as jest.Mock) + .mockReturnValueOnce([{ id: 'smell1' }]) // file1.py has smells + .mockReturnValueOnce([]); // file2.py is clean + + await initializeStatusesFromCache( + context, + smellsCacheManager, + smellsViewProvider, + ); + + expect(ecoOutput.info).toHaveBeenCalledWith( + expect.stringContaining( + '2 valid files (1 with smells, 1 clean), 2 files removed from cache', + ), + ); + }); +}); diff --git a/test/utils/smellsData.test.ts b/test/utils/smellsData.test.ts new file mode 100644 index 0000000..fdff91b --- /dev/null +++ b/test/utils/smellsData.test.ts @@ -0,0 +1,211 @@ +// smellsData.test.ts +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import * as path from 'path'; +import { + loadSmells, + saveSmells, + getFilterSmells, + getEnabledSmells, + getAcronymByMessageId, + getNameByMessageId, + getDescriptionByMessageId, + FilterSmellConfig, +} from '../../src/utils/smellsData'; + +// Mock the modules +jest.mock('vscode'); +jest.mock('fs'); +jest.mock('path'); + +const mockSmellsConfig: Record = { + 'long-parameter-list': { + name: 'Long Parameter List', + message_id: 'R0913', + acronym: 'LPL', + smell_description: 'Method has too many parameters', + enabled: true, + analyzer_options: { + max_params: { + label: 'Maximum Parameters', + description: 'Maximum allowed parameters', + value: 5, + }, + }, + }, + 'duplicate-code': { + name: 'Duplicate Code', + message_id: 'R0801', + acronym: 'DC', + smell_description: 'Code duplication detected', + enabled: false, + }, +}; + +// Mock console.error to prevent test output pollution +const originalConsoleError = console.error; +beforeAll(() => { + console.error = jest.fn(); +}); + +afterAll(() => { + console.error = originalConsoleError; +}); + +describe('smellsData', () => { + beforeEach(() => { + jest.clearAllMocks(); + + // Setup default mocks + (fs.existsSync as jest.Mock).mockReturnValue(true); + (fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(mockSmellsConfig)); + (fs.writeFileSync as jest.Mock).mockImplementation(() => {}); + + // Mock path.join to return predictable paths + (path.join as jest.Mock).mockImplementation((...args: string[]) => + args.join('/').replace(/\\/g, '/'), + ); + }); + + describe('loadSmells', () => { + it('should load smells configuration successfully', () => { + loadSmells('working'); + + // Update path expectation to match actual implementation + expect(fs.readFileSync).toHaveBeenCalledWith( + expect.stringContaining('data/working_smells_config.json'), + 'utf-8', + ); + expect(vscode.window.showErrorMessage).not.toHaveBeenCalled(); + }); + + it('should show error message when file is missing', () => { + (fs.existsSync as jest.Mock).mockReturnValue(false); + + loadSmells('working'); + + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith( + 'Configuration file missing: smells.json could not be found.', + ); + }); + + it('should show error message when file parsing fails', () => { + (fs.readFileSync as jest.Mock).mockImplementation(() => { + throw new Error('Parse error'); + }); + + loadSmells('working'); + + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith( + 'Error loading smells.json. Please check the file format.', + ); + expect(console.error).toHaveBeenCalledWith( + 'ERROR: Failed to parse smells.json', + expect.any(Error), + ); + }); + }); + + describe('saveSmells', () => { + it('should save smells configuration successfully', () => { + saveSmells(mockSmellsConfig); + + // Update path expectation to match actual implementation + expect(fs.writeFileSync).toHaveBeenCalledWith( + expect.stringContaining('data/working_smells_config.json'), + JSON.stringify(mockSmellsConfig, null, 2), + ); + expect(vscode.window.showErrorMessage).not.toHaveBeenCalled(); + }); + + it('should show error message when file write fails', () => { + (fs.writeFileSync as jest.Mock).mockImplementation(() => { + throw new Error('Write error'); + }); + + saveSmells(mockSmellsConfig); + + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith( + 'Error saving smells.json.', + ); + expect(console.error).toHaveBeenCalledWith( + 'ERROR: Failed to write smells.json', + expect.any(Error), + ); + }); + }); + + describe('getFilterSmells', () => { + it('should return the loaded filter smells', () => { + loadSmells('working'); + const result = getFilterSmells(); + + expect(result).toEqual(mockSmellsConfig); + }); + }); + + describe('getEnabledSmells', () => { + it('should return only enabled smells with parsed options', () => { + loadSmells('working'); + const result = getEnabledSmells(); + + expect(result).toEqual({ + 'long-parameter-list': { + message_id: 'R0913', + acronym: 'LPL', + options: { + max_params: 5, + }, + }, + }); + }); + }); + + describe('getAcronymByMessageId', () => { + it('should return the correct acronym for a message ID', () => { + loadSmells('working'); + const result = getAcronymByMessageId('R0913'); + + expect(result).toBe('LPL'); + }); + + it('should return undefined for unknown message ID', () => { + loadSmells('working'); + const result = getAcronymByMessageId('UNKNOWN'); + + expect(result).toBeUndefined(); + }); + }); + + describe('getNameByMessageId', () => { + it('should return the correct name for a message ID', () => { + loadSmells('working'); + const result = getNameByMessageId('R0913'); + + expect(result).toBe('Long Parameter List'); + }); + + it('should return undefined for unknown message ID', () => { + loadSmells('working'); + const result = getNameByMessageId('UNKNOWN'); + + expect(result).toBeUndefined(); + }); + }); + + describe('getDescriptionByMessageId', () => { + it('should return the correct description for a message ID', () => { + loadSmells('working'); + const result = getDescriptionByMessageId('R0913'); + + expect(result).toBe('Method has too many parameters'); + }); + + it('should return undefined for unknown message ID', () => { + loadSmells('working'); + const result = getDescriptionByMessageId('UNKNOWN'); + + expect(result).toBeUndefined(); + }); + }); +}); diff --git a/test/utils/trackedDiffEditors.test.ts b/test/utils/trackedDiffEditors.test.ts new file mode 100644 index 0000000..252f58b --- /dev/null +++ b/test/utils/trackedDiffEditors.test.ts @@ -0,0 +1,115 @@ +// utils/trackedDiffEditors.test.ts +import * as vscode from 'vscode'; +import { + registerDiffEditor, + isTrackedDiffEditor, + closeAllTrackedDiffEditors, + trackedDiffs, +} from '../../src/utils/trackedDiffEditors'; + +// Mock the vscode API +jest.mock('vscode', () => ({ + window: { + tabGroups: { + close: jest.fn().mockResolvedValue(true), + all: [], + }, + }, + Uri: { + parse: jest.fn(), + }, +})); + +describe('trackedDiffEditors', () => { + const mockUri1 = { toString: () => 'file:///test1.txt' } as vscode.Uri; + const mockUri2 = { toString: () => 'file:///test2.txt' } as vscode.Uri; + const mockUri3 = { toString: () => 'file:///test3.txt' } as vscode.Uri; + + beforeEach(() => { + // Clear the trackedDiffs set before each test + trackedDiffs.clear(); + // Reset the mock implementation + (vscode.window.tabGroups.close as jest.Mock).mockClear().mockResolvedValue(true); + // Reset tab groups mock + (vscode.window.tabGroups.all as any) = []; + }); + + describe('registerDiffEditor', () => { + it('should register a diff editor with given URIs', () => { + registerDiffEditor(mockUri1, mockUri2); + expect(isTrackedDiffEditor(mockUri1, mockUri2)).toBe(true); + }); + + it('should not register unrelated URIs', () => { + registerDiffEditor(mockUri1, mockUri2); + expect(isTrackedDiffEditor(mockUri1, mockUri3)).toBe(false); + expect(isTrackedDiffEditor(mockUri2, mockUri3)).toBe(false); + }); + }); + + describe('isTrackedDiffEditor', () => { + it('should return true for registered diff editors', () => { + registerDiffEditor(mockUri1, mockUri2); + expect(isTrackedDiffEditor(mockUri1, mockUri2)).toBe(true); + }); + + it('should return false for unregistered diff editors', () => { + expect(isTrackedDiffEditor(mockUri1, mockUri2)).toBe(false); + }); + + it('should be case sensitive for URIs', () => { + const mockUriLower = { toString: () => 'file:///test1.txt' } as vscode.Uri; + const mockUriUpper = { toString: () => 'FILE:///TEST1.TXT' } as vscode.Uri; + registerDiffEditor(mockUriLower, mockUri2); + expect(isTrackedDiffEditor(mockUriUpper, mockUri2)).toBe(false); + }); + }); + + describe('closeAllTrackedDiffEditors', () => { + it('should close all tracked diff editors', async () => { + // Setup mock tabs + const mockTab1 = { + input: { original: mockUri1, modified: mockUri2 }, + }; + const mockTab2 = { + input: { original: mockUri3, modified: mockUri2 }, + }; + const mockTab3 = { + input: { somethingElse: true }, + }; + + // Mock the tabGroups.all + (vscode.window.tabGroups.all as any) = [ + { tabs: [mockTab1, mockTab2] }, + { tabs: [mockTab3] }, + ]; + + registerDiffEditor(mockUri1, mockUri2); + + await closeAllTrackedDiffEditors(); + + expect(vscode.window.tabGroups.close).toHaveBeenCalledTimes(1); + expect(vscode.window.tabGroups.close).toHaveBeenCalledWith(mockTab1, true); + }); + + it('should clear all tracked diffs after closing', async () => { + registerDiffEditor(mockUri1, mockUri2); + (vscode.window.tabGroups.all as any) = []; + + await closeAllTrackedDiffEditors(); + + expect(isTrackedDiffEditor(mockUri1, mockUri2)).toBe(false); + }); + + it('should handle empty tabs', async () => { + // Ensure no tabs exist + (vscode.window.tabGroups.all as any) = []; + // Don't register any editors for this test + + await closeAllTrackedDiffEditors(); + + expect(vscode.window.tabGroups.close).not.toHaveBeenCalled(); + expect(trackedDiffs.size).toBe(0); + }); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 8ce50e7..da4d5ef 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,14 +1,21 @@ { "compilerOptions": { - "module": "Node16", - "target": "ES2022", - "lib": [ - "ES2022" - ], - "sourceMap": true, - "rootDir": "src", - "strict": true, /* enable all strict type-checking options */ - "typeRoots": ["./node_modules/@types", "./types"] + "module": "Node16", + "target": "ES2022", + "lib": [ + "ES2022", + "DOM", + "DOM.Iterable" + ], + "sourceMap": true, + "rootDirs": ["./src", "./test"], + "outDir": "dist", + "strict": true, + "typeRoots": ["./node_modules/@types", "./types"], + "forceConsistentCasingInFileNames": true, + "allowSyntheticDefaultImports": true }, - "include": ["src/global.d.ts", "src/**/*"] -} + "include": ["./src/global.d.ts", "src/**/*.ts", "test/**/*.ts"], + "exclude": ["node_modules", "dist"] + } + \ No newline at end of file diff --git a/vsc-extension-quickstart.md b/vsc-extension-quickstart.md deleted file mode 100644 index f518bb8..0000000 --- a/vsc-extension-quickstart.md +++ /dev/null @@ -1,48 +0,0 @@ -# Welcome to your VS Code Extension - -## What's in the folder - -* This folder contains all of the files necessary for your extension. -* `package.json` - this is the manifest file in which you declare your extension and command. - * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. -* `src/extension.ts` - this is the main file where you will provide the implementation of your command. - * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. - * We pass the function containing the implementation of the command as the second parameter to `registerCommand`. - -## Setup - -* install the recommended extensions (amodio.tsl-problem-matcher, ms-vscode.extension-test-runner, and dbaeumer.vscode-eslint) - - -## Get up and running straight away - -* Press `F5` to open a new window with your extension loaded. -* Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. -* Set breakpoints in your code inside `src/extension.ts` to debug your extension. -* Find output from your extension in the debug console. - -## Make changes - -* You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. -* You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. - - -## Explore the API - -* You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`. - -## Run tests - -* Install the [Extension Test Runner](https://marketplace.visualstudio.com/items?itemName=ms-vscode.extension-test-runner) -* Run the "watch" task via the **Tasks: Run Task** command. Make sure this is running, or tests might not be discovered. -* Open the Testing view from the activity bar and click the Run Test" button, or use the hotkey `Ctrl/Cmd + ; A` -* See the output of the test result in the Test Results view. -* Make changes to `src/test/extension.test.ts` or create new test files inside the `test` folder. - * The provided test runner will only consider files matching the name pattern `**.test.ts`. - * You can create folders inside the `test` folder to structure your tests any way you want. - -## Go further - -* Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension). -* [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VS Code extension marketplace. -* Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration). diff --git a/webpack.config.js b/webpack.config.js index 37d7024..320a4f0 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,48 +1,34 @@ -//@ts-check - -'use strict'; - const path = require('path'); -//@ts-check -/** @typedef {import('webpack').Configuration} WebpackConfig **/ - -/** @type WebpackConfig */ -const extensionConfig = { - target: 'node', // VS Code extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/ - mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production') - - entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ +module.exports = { + target: 'node', + entry: { + extension: './src/extension.ts', + install: './src/install.ts' // Separate entry point for install script + }, output: { - // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ path: path.resolve(__dirname, 'dist'), - filename: 'extension.js', + filename: '[name].js', libraryTarget: 'commonjs2' }, - externals: { - vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ - // modules added here also need to be added in the .vscodeignore file - }, resolve: { - // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader - extensions: ['.ts', '.js'] + extensions: ['.ts', '.js'], }, + externals: [ + { vscode: 'commonjs vscode' }, + ], module: { rules: [ { test: /\.ts$/, exclude: /node_modules/, - use: [ - { - loader: 'ts-loader' - } - ] - } - ] + use: 'ts-loader', + }, + ], }, - devtool: 'nosources-source-map', + mode: 'development', + devtool: 'source-map', infrastructureLogging: { - level: "log", // enables logging required for problem matchers + level: 'log' // enables logging required for problem matchers }, -}; -module.exports = [ extensionConfig ]; \ No newline at end of file +}; \ No newline at end of file