diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..3ec154d --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,116 @@ +name: Tests & Linting + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + lint-js: + name: ๐Ÿงผ ESLint (JS/TS) + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install Node.js dependencies + run: npm install + + - name: Run ESLint + run: npm run lint:js + + lint-py: + name: ๐Ÿ Ruff (Python) + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install Python Dependencies + run: | + python -m pip install --upgrade pip + pip install ruff + + - name: Ruff check + run: npm run lint:py + + parser-tests: + name: ๐Ÿงช Parser Tests + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install Python Dependencies + run: | + python -m pip install --upgrade pip + pip install -r parser/requirements.txt + pip install pytest ruff + + - name: Run parser tests + run: npm run test:parser + + webview-tests: + name: ๐ŸŽจ Webview Visual Tests + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install Node.js dependencies + run: npm install + + - name: Install Playwright browsers + run: npx playwright install --with-deps + + - name: Run webview tests (headless) + run: xvfb-run -a npm run test:webview + + extension-tests: + name: ๐Ÿงฉ Extension Tests (VS Code) + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install Node.js dependencies + run: npm install + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install Python Dependencies + run: | + python -m pip install --upgrade pip + pip install -r parser/requirements.txt + + - name: Run extension tests + run: xvfb-run -a npm run test:src \ No newline at end of file diff --git a/.gitignore b/.gitignore index ec5efde..7127a2a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ Thumbs.db **/node_modules/ **/out/ */.vs/ +**/.venv/ **/.vscode-test/ tsconfig.lsif.json *.lsif diff --git a/CHANGELOG b/CHANGELOG index ecf38dd..2549cc6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/). --- +## [0.1.8] - 2025-08-08 + +### Added +- `npm run` scripts for testing & linting +- `ruff` and `eslint` for linting +- GitHub Actions for testing & linting +- Detailed `CONTRIBUTING.md` + ## [0.1.7] - 2025-08-04 ### Added diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..8793519 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,126 @@ +# ๐Ÿค Contributing to LaunchMap + +Thank you for your interest in contributing to LaunchMap! +Contributions of all kinds, features, bug fixes, tests, docs, and feedback are welcome! + +--- + +## ๐Ÿ› ๏ธ Project Structure +```txt +launchmap/ +โ”œโ”€โ”€ parser/ # Python-based static launch file parser +โ”œโ”€โ”€ src/ # VS Code extension (TypeScript) +โ”œโ”€โ”€ webview/ # Webview frontend (HTML/JS) +``` + +--- + +## โš™๏ธ Setup Instructions + +### Installation + +```bash +# Clone the repo +git clone https://github.com/Kodo-Robotics/launchmap +cd launchmap + +# Python setup +python3 -m venv .venv +source .venv/bin/activate +pip install -r parser/requirements.txt + +# Node setup +npm install +``` + +### Running + +Here is how you can get the extension running and see the output: + +1. Compile the code +```bash +npm run compile +``` +This converts the TypeScript code into JavaScript so VS Code can run it. + +2. Start the extension in a debug VS Code window + - Open the file `src/extension.ts`. + - Press F5 (or go to Run and Debug โ†’ Launch Extension). + +This launches a new VS Code window with the extension loaded in "dev mode" so you can test your changes live. + +3. Open a sample launch file + - In the new debug VS Code window, navigate to: +`parser/tests/real_cases/launch_files/` + - Open any `.launch.py` file (these are real world test cases). + +4. Run the LaunchMap visualizer + - Press `Cmd + Shift + P` (or `Ctrl + Shift + P` on Windows/Linux). + - Search for `LaunchMap: Open Launch Visualizer` and run it. + +You should now see the visualization output. + +--- + +## ๐Ÿงน Linting & Code Style + +We use: +- `ruff` for Python (ultra-fast linter + formatter) +- `eslint` for TypeScript/JavaScript + +### ๐Ÿ” Run Python Lint (parser) + +```bash +# Test +npm run lint:py + +# Fix +npm run lint:py:fix +``` + +### ๐Ÿ” Run JS/TS Lint (extension + webview) + +```bash +# Test +npm run lint:js + +# Fix +npm run lint:js:fix +``` + +--- + +## ๐Ÿงช Running Tests + +```bash +# Parser Tests +npm run test:parser + +# Webview Visual Tests +npm run test:webview + +# Extension Integration Tests +npm run test:src +``` + +### โš ๏ธ Notes on Snapshot Testing +- Visual snapshots are stored under `webview/tests/__screenshots__/` +- Snapshots may differ between macOS, Windows, and Linux +- Snapshots are only validated on PRs in CI (Linux) +- If you are on macOS/Windows, avoid committing updated snapshots unless necessary + +--- + +## ๐Ÿ’ก Tips +- Prefer small, focused PRs +- Follow lint rules strictly (ruff and eslint) +- Playwright tests are validated automatically on PRs +- Use `npx playwright test --update-snapshots` only on Linux for visual diffs + +--- + +## ๐Ÿ“ฌ Questions? +- Join our [Discord Server](https://discord.gg/EK3pHZxrKy) +- Ask on GitHub + +Thanks again for contributing! ๐Ÿš€ \ No newline at end of file diff --git a/README.md b/README.md index 4ce9b69..35360b4 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ [![VSCode Marketplace](https://img.shields.io/visual-studio-marketplace/v/KodoRobotics.launchmap?label=VSCode%20Marketplace)](https://marketplace.visualstudio.com/items?itemName=KodoRobotics.launchmap) [![License](https://img.shields.io/github/license/Kodo-Robotics/launchmap?color=blue)](./LICENSE) [![Join Discord](https://img.shields.io/badge/Discord-Join%20Community-5865F2?logo=discord&logoColor=white)](https://discord.gg/EK3pHZxrKy) +[![Test Status](https://github.com/Kodo-Robotics/launchmap/actions/workflows/tests.yml/badge.svg)](https://github.com/Kodo-Robotics/launchmap/actions/workflows/tests.yml) **LaunchMap** is a Visual Studio Code extension that lets you visualize the structure of ROS 2 launch files as interactive graphs directly inside VSCode. @@ -177,7 +178,7 @@ New to the ROS 2 ecosystem? Here are some great resources to get you started: ## ๐Ÿค Contributing Contributions are welcome! -Please open an issue, suggest a feature, or submit a pull request. +Refer to [CONTRIBUTING.md](CONTRIBUTING.md) for more details. --- diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..99ee84c --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,32 @@ +import js from '@eslint/js'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; +import { defineConfig } from 'eslint/config'; + +export default defineConfig([ + { + files: ['**/*.{js,mjs,cjs,ts,mts,cts}'], + plugins: { js }, + extends: ['js/recommended'], + languageOptions: { + globals: { + ...globals.browser, + ...globals.node + } + }, + rules: { + indent: ['error', 2], + quotes: ['error', 'single'], + semi: ['error', 'always'], + 'comma-spacing': ['error', { before: false, after: true }], + 'keyword-spacing': ['error', { before: true, after: true }], + 'space-before-blocks': ['error', 'always'], + 'space-in-parens': ['error', 'never'], + 'array-bracket-spacing': ['error', 'never'], + 'object-curly-spacing': ['error', 'always'], + 'no-trailing-spaces': 'error', + 'eol-last': ['error', 'always'], + } + }, + tseslint.configs.recommended +]); diff --git a/package-lock.json b/package-lock.json index 91f3b8b..8dd44c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,28 +1,36 @@ { "name": "launchmap", - "version": "0.1.7", + "version": "0.1.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "launchmap", - "version": "0.1.7", + "version": "0.1.8", "license": "Apache-2.0", "dependencies": { "which": "^5.0.0" }, "devDependencies": { + "@eslint/js": "^9.32.0", "@playwright/test": "^1.54.2", "@types/mocha": "^10.0.10", "@types/node": "^24.0.1", "@types/vscode": "^1.70.0", "@types/which": "^3.0.4", "@vscode/test-electron": "^2.5.2", + "eslint": "^9.32.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-prettier": "^5.5.4", + "globals": "^16.3.0", "mocha": "^11.7.1", "playwright": "^1.54.2", "serve-handler": "^6.1.6", "ts-node": "^10.9.2", - "typescript": "^4.7.4" + "typescript": "^4.7.4", + "typescript-eslint": "^8.39.0" }, "engines": { "vscode": "^1.70.0" @@ -41,6 +49,274 @@ "node": ">=12" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/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-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@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" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "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": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@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": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.32.0.tgz", + "integrity": "sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "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, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", + "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@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": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "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": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -87,6 +363,44 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -98,6 +412,19 @@ "node": ">=14" } }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, "node_modules/@playwright/test": { "version": "1.54.2", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.54.2.tgz", @@ -114,6 +441,13 @@ "node": ">=18" } }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -142,6 +476,27 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/mocha": { "version": "10.0.10", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", @@ -173,61 +528,320 @@ "dev": true, "license": "MIT" }, - "node_modules/@vscode/test-electron": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.5.2.tgz", - "integrity": "sha512-8ukpxv4wYe0iWMRQU18jhzJOHkeGKbnw7xWRX3Zw1WJA4cEKbHcmmLPdPrPtL6rhDcrlCZN+xKRpv09n4gRHYg==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.0.tgz", + "integrity": "sha512-bhEz6OZeUR+O/6yx9Jk6ohX6H9JSFTaiY0v9/PuKT3oGK0rn0jNplLmyFUGV+a9gfYnVNwGDwS/UkLIuXNb2Rw==", "dev": true, "license": "MIT", "dependencies": { - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.5", - "jszip": "^3.10.1", - "ora": "^8.1.0", - "semver": "^7.6.2" + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.39.0", + "@typescript-eslint/type-utils": "8.39.0", + "@typescript-eslint/utils": "8.39.0", + "@typescript-eslint/visitor-keys": "8.39.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" }, "engines": { - "node": ">=16" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.39.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, "engines": { - "node": ">=0.4.0" + "node": ">= 4" } }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "node_modules/@typescript-eslint/parser": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.39.0.tgz", + "integrity": "sha512-g3WpVQHngx0aLXn6kfIYCZxM6rRJlWzEkVpqEFLT3SgEDsp9cpCbxxgwnE504q4H+ruSDh/VGS6nqZIDynP+vg==", "dev": true, "license": "MIT", "dependencies": { - "acorn": "^8.11.0" + "@typescript-eslint/scope-manager": "8.39.0", + "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/typescript-estree": "8.39.0", + "@typescript-eslint/visitor-keys": "8.39.0", + "debug": "^4.3.4" }, "engines": { - "node": ">=0.4.0" + "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 <6.0.0" } }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "node_modules/@typescript-eslint/project-service": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.39.0.tgz", + "integrity": "sha512-CTzJqaSq30V/Z2Og9jogzZt8lJRR5TKlAdXmWgdu4hgcC9Kww5flQ+xFvMxIBWVNdxJO7OifgdOK4PokMIWPew==", "dev": true, "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.39.0", + "@typescript-eslint/types": "^8.39.0", + "debug": "^4.3.4" + }, "engines": { - "node": ">= 14" - } - }, - "node_modules/ansi-regex": { - "version": "6.1.0", + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.39.0.tgz", + "integrity": "sha512-8QOzff9UKxOh6npZQ/4FQu4mjdOCGSdO3p44ww0hk8Vu+IGbg0tB/H1LcTARRDzGCC8pDGbh2rissBuuoPgH8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/visitor-keys": "8.39.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/tsconfig-utils": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.0.tgz", + "integrity": "sha512-Fd3/QjmFV2sKmvv3Mrj8r6N8CryYiCS8Wdb/6/rgOXAWGcFuc+VkQuG28uk/4kVNVZBQuuDHEDUpo/pQ32zsIQ==", + "dev": true, + "license": "MIT", + "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 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.39.0.tgz", + "integrity": "sha512-6B3z0c1DXVT2vYA9+z9axjtc09rqKUPRmijD5m9iv8iQpHBRYRMBcgxSiKTZKm6FwWw1/cI4v6em35OsKCiN5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/typescript-estree": "8.39.0", + "@typescript-eslint/utils": "8.39.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.39.0.tgz", + "integrity": "sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.0.tgz", + "integrity": "sha512-ndWdiflRMvfIgQRpckQQLiB5qAKQ7w++V4LlCHwp62eym1HLB/kw7D9f2e8ytONls/jt89TEasgvb+VwnRprsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.39.0", + "@typescript-eslint/tsconfig-utils": "8.39.0", + "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/visitor-keys": "8.39.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.1.0" + }, + "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 <6.0.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.39.0.tgz", + "integrity": "sha512-4GVSvNA0Vx1Ktwvf4sFE+exxJ3QGUorQG1/A5mRfRNZtkBT2xrA/BCO2H0eALx/PnvCS6/vmYwRdDA41EoffkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.39.0", + "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/typescript-estree": "8.39.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.0.tgz", + "integrity": "sha512-ldgiJ+VAhQCfIjeOgu8Kj5nSxds0ktPOSO9p4+0VDH2R2pLvQraaM5Oen2d7NxzMCm+Sn/vJT+mv2H5u6b/3fA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.39.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vscode/test-electron": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.5.2.tgz", + "integrity": "sha512-8ukpxv4wYe0iWMRQU18jhzJOHkeGKbnw7xWRX3Zw1WJA4cEKbHcmmLPdPrPtL6rhDcrlCZN+xKRpv09n4gRHYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "jszip": "^3.10.1", + "ora": "^8.1.0", + "semver": "^7.6.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, @@ -269,6 +883,191 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.10.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz", + "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -286,6 +1085,19 @@ "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", @@ -293,558 +1105,2126 @@ "dev": true, "license": "ISC" }, - "node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/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/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/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/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "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, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "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/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/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/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/content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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-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, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/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": "ISC" + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "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/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/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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/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/es-abstract": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "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.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.32.0.tgz", + "integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", + "@eslint/core": "^0.15.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.32.0", + "@eslint/plugin-kit": "^0.3.4", + "@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.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.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.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/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/eslint-plugin-import/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": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-jsx-a11y/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/eslint-plugin-prettier": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz", + "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.11.7" + }, + "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": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "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": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "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/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "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.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "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/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-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, + "license": "Apache-2.0" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "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/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/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/globals": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz", + "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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/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": ">= 14" + } + }, + "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": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "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", "engines": { - "node": ">= 0.8" + "node": ">= 4" } }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "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" + }, + "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": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, "engines": { - "node": ">=10" + "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=0.8.19" } }, - "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/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": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" }, "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", "dev": true, "license": "MIT", "dependencies": { - "readdirp": "^4.0.1" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { - "node": ">= 14.16.0" + "node": ">= 0.4" }, "funding": { - "url": "https://paulmillr.com/funding/" + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", "dev": true, "license": "MIT", "dependencies": { - "restore-cursor": "^5.0.0" + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { - "node": ">=18" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "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/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "dev": true, "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, "engines": { - "node": ">=6" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "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/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": ">=12" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "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" + "node": ">= 0.4" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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/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": { - "ansi-regex": "^5.0.1" + "hasown": "^2.0.2" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "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/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "dev": true, "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": ">=7.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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/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/content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", + "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": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=0.10.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/create-require": { + "node_modules/is-finalizationregistry": { "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, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", "dev": true, "license": "MIT", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "call-bound": "^1.0.3" }, "engines": { - "node": ">= 8" - } - }, - "node_modules/cross-spawn/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": "ISC" - }, - "node_modules/cross-spawn/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" + "node": ">= 0.4" }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "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": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=8" } }, - "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-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/diff": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", - "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" + "url": "https://github.com/sponsors/ljharb" } }, - "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/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/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "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": { + "is-extglob": "^2.1.1" + }, "engines": { - "node": ">=6" + "node": ">=0.10.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-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", "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "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/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true, "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "engines": { + "node": ">= 0.4" }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "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": "BSD-3-Clause", - "bin": { - "flat": "cli.js" + "license": "MIT", + "engines": { + "node": ">=0.12.0" } }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": ">=14" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "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, - "hasInstallScript": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "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/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "dev": true, - "license": "ISC", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "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/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", "dev": true, - "license": "ISC", + "license": "MIT", "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" + "call-bound": "^1.0.3" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "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/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, "engines": { - "node": ">=8" - } - }, - "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": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "dev": true, "license": "MIT", "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" }, "engines": { - "node": ">= 14" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dev": true, "license": "MIT", "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" + "which-typed-array": "^1.1.16" }, "engines": { - "node": ">= 14" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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" - }, - "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": "ISC" - }, - "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/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": "MIT", "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "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/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/isarray": { @@ -892,6 +3272,56 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "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": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/jszip": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", @@ -905,6 +3335,50 @@ "setimmediate": "^1.0.5" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", @@ -931,6 +3405,13 @@ "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", @@ -960,7 +3441,41 @@ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true, - "license": "ISC" + "license": "ISC" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } }, "node_modules/mime-db": { "version": "1.33.0", @@ -1014,6 +3529,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -1067,6 +3592,110 @@ "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", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/onetime": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", @@ -1083,6 +3712,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/ora": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", @@ -1188,6 +3835,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -1234,6 +3899,19 @@ "dev": true, "license": "(MIT AND Zlib)" }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -1261,6 +3939,13 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", @@ -1292,6 +3977,19 @@ "dev": true, "license": "ISC" }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/playwright": { "version": "1.54.2", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.54.2.tgz", @@ -1324,6 +4022,56 @@ "node": ">=18" } }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "peer": 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, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -1331,6 +4079,37 @@ "dev": true, "license": "MIT" }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -1381,6 +4160,50 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -1391,6 +4214,37 @@ "node": ">=0.10.0" } }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/restore-cursor": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", @@ -1408,6 +4262,68 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -1415,6 +4331,48 @@ "dev": true, "license": "MIT" }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -1461,21 +4419,70 @@ "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/serve-handler/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/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/serve-handler/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/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": "*" + "node": ">= 0.4" } }, "node_modules/setimmediate": { @@ -1508,6 +4515,82 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -1534,6 +4617,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -1608,6 +4705,80 @@ "node": ">=8" } }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/strip-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", @@ -1648,6 +4819,16 @@ "node": ">=8" } }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -1677,6 +4858,61 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/synckit": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", + "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -1731,6 +4967,110 @@ "node": ">=0.3.1" } }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", @@ -1745,6 +5085,49 @@ "node": ">=4.2.0" } }, + "node_modules/typescript-eslint": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.39.0.tgz", + "integrity": "sha512-lH8FvtdtzcHJCkMOKnN73LIn6SLTpoojgJqDAxPm1jCR14eWSGPX8ul/gggBdPMk/d5+u9V854vTYQ8T5jF/1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.39.0", + "@typescript-eslint/parser": "8.39.0", + "@typescript-eslint/typescript-estree": "8.39.0", + "@typescript-eslint/utils": "8.39.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/undici-types": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", @@ -1752,6 +5135,16 @@ "dev": true, "license": "MIT" }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -1781,6 +5174,112 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/workerpool": { "version": "9.3.3", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.3.tgz", diff --git a/package.json b/package.json index b49fb0d..fe71fde 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "launchmap", "displayName": "LaunchMap", "description": "Visualize ROS2 Launch Files", - "version": "0.1.7", + "version": "0.1.8", "publisher": "kodorobotics", "icon": "assets/launchmap-logo.png", "bugs": { @@ -65,23 +65,37 @@ }, "scripts": { "compile": "tsc", + "lint:py": "ruff check parser/", + "lint:py:fix": "ruff check --fix parser/", + "lint:js": "eslint webview/ src/ --ext .js,.ts,.tsx", + "lint:js:fix": "eslint webview/ src/ --ext .js,.ts,.tsx --fix", + "lint": "npm run lint:py && npm run lint:js", + "lint:fix": "npm run lint:py:fix && npm run lint:js:fix", "test:src": "npm run compile && node dist/test/runTest.js", "test:parser": "cd parser/tests && pytest . --maxfail=1 --disable-warnings -q", "test:webview": "cd webview/tests && npx playwright test", "test:all": "npm run test:src && npm run test:parser && npm run test:webview" }, "devDependencies": { + "@eslint/js": "^9.32.0", "@playwright/test": "^1.54.2", "@types/mocha": "^10.0.10", "@types/node": "^24.0.1", "@types/vscode": "^1.70.0", "@types/which": "^3.0.4", "@vscode/test-electron": "^2.5.2", + "eslint": "^9.32.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-prettier": "^5.5.4", + "globals": "^16.3.0", "mocha": "^11.7.1", "playwright": "^1.54.2", "serve-handler": "^6.1.6", "ts-node": "^10.9.2", - "typescript": "^4.7.4" + "typescript": "^4.7.4", + "typescript-eslint": "^8.39.0" }, "dependencies": { "which": "^5.0.0" diff --git a/parse.py b/parse.py index d55c2a9..e3d8fe8 100644 --- a/parse.py +++ b/parse.py @@ -18,10 +18,13 @@ from parser.entrypoint.user_interface import parse_and_format_launch_file from parser.plugin_loader import load_user_handlers_from_directory + def main(): parser = argparse.ArgumentParser(description="Parse a ROS2 launch file.") parser.add_argument("launch_file", help="Path to launch file to parse.") - parser.add_argument("--plugin-dir", help="Directory containing user-defined custom handlers.") + parser.add_argument( + "--plugin-dir", help="Directory containing user-defined custom handlers." + ) args = parser.parse_args() @@ -35,5 +38,6 @@ def main(): print(f"Error: {e}") sys.exit(1) + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/parser/context.py b/parser/context.py index c6a9581..6dc40f4 100644 --- a/parser/context.py +++ b/parser/context.py @@ -12,10 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +import ast from collections import defaultdict -from parser.introspection.tracker import IntrospectionTracker from typing import Any -import ast + +from parser.introspection.tracker import IntrospectionTracker + class ParseContext: def __init__(self, introspection: IntrospectionTracker = None): @@ -46,45 +48,45 @@ def __init__(self, introspection: IntrospectionTracker = None): ## Variable Management def define_variable(self, name: str, value: Any): - """ Define or update a variable in context. """ + """Define or update a variable in context.""" self.variables[name] = value def has_variable(self, name: str) -> bool: - """ Check variable exists in context """ + """Check variable exists in context""" return name in self.variables def lookup_variable(self, name: str) -> Any: - """ Resolve a variable by name. Raises if undefined. """ + """Resolve a variable by name. Raises if undefined.""" if name not in self.variables: raise NameError(f"Variable '{name}' is not defined in context.") return self.variables[name] - + ## Function API def define_function(self, name: str, fn_def: ast.FunctionDef): - """ Define or update a function in context. """ + """Define or update a function in context.""" self.functions[name] = fn_def def has_function(self, name: str) -> bool: - """ Check function exists in context. """ + """Check function exists in context.""" return name in self.functions def lookup_function(self, name: str) -> ast.FunctionDef | None: - """ Return the AST of a function by name, or None if not found. """ + """Return the AST of a function by name, or None if not found.""" return self.functions.get(name) - + ## Namespace stack - + def push_namespace(self, ns: str): self.namespace_stack.append(ns) - + def pop_namespace(self): if self.namespace_stack: self.namespace_stack.pop() def current_namespace(self) -> str | None: return "/".join(self.namespace_stack) if self.namespace_stack else None - + ## Composable node groups def register_composable_node_group(self, container_name: str, container_metadata: dict): @@ -99,25 +101,22 @@ def get_composable_node_groups(self) -> list[dict]: if not data["composable_nodes"]: continue - entry = { - "target_container": name, - "composable_nodes": data["composable_nodes"] - } + entry = {"target_container": name, "composable_nodes": data["composable_nodes"]} for key, value in data.items(): if key in ("composable_nodes",): continue if value not in (None, "", [], {}): entry[key] = value - + results.append(entry) return results - + ## Utility def get_func_name(self, func_node) -> str: - """ Resolve a dotted name from an AST function call. """ + """Resolve a dotted name from an AST function call.""" from parser.resolution.utils import get_func_name + return get_func_name(func_node) - \ No newline at end of file diff --git a/parser/entrypoint/parser_runner.py b/parser/entrypoint/parser_runner.py index f651548..d7a2088 100644 --- a/parser/entrypoint/parser_runner.py +++ b/parser/entrypoint/parser_runner.py @@ -13,10 +13,12 @@ # limitations under the License. import ast + from parser.context import ParseContext from parser.parser.utils.ast_utils import collect_function_defs from parser.resolution.resolution_engine import ResolutionEngine + def parse_launch_file(filepath: str) -> dict: """ Entrypoint: parses a launch file and returns structured output @@ -49,7 +51,7 @@ def parse_launch_file(filepath: str) -> dict: main_fn = context.lookup_function("generate_launch_description") if not main_fn: raise ValueError("No generate_launch_description() function found.") - + parsed.extend(_parse_launch_function_body(main_fn.body, context, engine)) return { @@ -57,14 +59,19 @@ def parse_launch_file(filepath: str) -> dict: "parsed": parsed, "used_launch_config": sorted(context.introspection.used_launch_configs), "declared_arguments": sorted(context.introspection.declared_launch_args.keys()), - "undeclared_launch_configurations": sorted(context.introspection.get_undeclared_launch_configs()), + "undeclared_launch_configurations": sorted( + context.introspection.get_undeclared_launch_configs() + ), "environment_variables": context.introspection.get_environment_variables(), "python_expressions": context.introspection.get_python_expressions(), "composable_node_containers": sorted(context.get_composable_node_groups()), - "additional_components": context.introspection.get_registered_entities() + "additional_components": context.introspection.get_registered_entities(), } -def _parse_launch_function_body(body: list[ast.stmt], context: ParseContext, engine: ResolutionEngine) -> list: + +def _parse_launch_function_body( + body: list[ast.stmt], context: ParseContext, engine: ResolutionEngine +) -> list: parsed = [] for stmt in body: if isinstance(stmt, ast.Assign): @@ -72,7 +79,7 @@ def _parse_launch_function_body(body: list[ast.stmt], context: ParseContext, eng elif isinstance(stmt, ast.If): engine.resolve(stmt) - + elif isinstance(stmt, ast.Expr): func_name = context.get_func_name(stmt.value.func) if func_name.endswith("add_action"): @@ -82,12 +89,12 @@ def _parse_launch_function_body(body: list[ast.stmt], context: ParseContext, eng parsed.append(result) else: engine.resolve(stmt) - + elif isinstance(stmt, ast.Return) and isinstance(stmt.value, ast.Call): resolved = engine.resolve(stmt.value) if isinstance(resolved, list): parsed.extend(resolved) elif resolved is not None: parsed.append(resolved) - - return parsed \ No newline at end of file + + return parsed diff --git a/parser/entrypoint/user_interface.py b/parser/entrypoint/user_interface.py index 7a2ac20..0de662e 100644 --- a/parser/entrypoint/user_interface.py +++ b/parser/entrypoint/user_interface.py @@ -13,11 +13,16 @@ # limitations under the License. from parser.entrypoint.parser_runner import parse_launch_file -from parser.parser.introspection_utils import (collect_environment_variable_usages, collect_launch_config_usages, - collect_event_handler_usages, collect_python_variable_usages) +from parser.parser.introspection_utils import ( + collect_environment_variable_usages, + collect_event_handler_usages, + collect_launch_config_usages, + collect_python_variable_usages, +) from parser.parser.postprocessing import simplify_launch_configurations from parser.parser.utils.common import group_entities_by_type + def parse_and_format_launch_file(filepath: str) -> dict: """ Parses a ROS2 launch file and return output in the standardized grouped format. @@ -57,4 +62,4 @@ def parse_and_format_launch_file(filepath: str) -> dict: if python_expression_usages: grouped["python_expression_usages"] = python_expression_usages - return simplify_launch_configurations(grouped) \ No newline at end of file + return simplify_launch_configurations(grouped) diff --git a/parser/full_launch_example.launch.py b/parser/full_launch_example.launch.py index 128c301..5c70e29 100644 --- a/parser/full_launch_example.launch.py +++ b/parser/full_launch_example.launch.py @@ -12,26 +12,31 @@ # See the License for the specific language governing permissions and # limitations under the License. + from launch import LaunchDescription -from launch.actions import DeclareLaunchArgument, GroupAction, IncludeLaunchDescription, OpaqueFunction +from launch.actions import ( + DeclareLaunchArgument, + GroupAction, + IncludeLaunchDescription, + OpaqueFunction, +) from launch.conditions import IfCondition +from launch.launch_description_sources import PythonLaunchDescriptionSource from launch.substitutions import LaunchConfiguration, PythonExpression -from launch_ros.actions import Node, ComposableNodeContainer +from launch_ros.actions import ComposableNodeContainer, Node from launch_ros.descriptions import ComposableNode -from launch.launch_description_sources import PythonLaunchDescriptionSource -import os def launch_setup(context, *args, **kwargs): - use_sim_time = LaunchConfiguration('use_sim_time').perform(context) - robot_description = f"..." + use_sim_time = LaunchConfiguration("use_sim_time").perform(context) + robot_description = "..." return [ Node( - package='robot_state_publisher', - executable='robot_state_publisher', - output='screen', - parameters=[{'use_sim_time': use_sim_time, 'robot_description': robot_description}] + package="robot_state_publisher", + executable="robot_state_publisher", + output="screen", + parameters=[{"use_sim_time": use_sim_time, "robot_description": robot_description}], ) ] @@ -39,37 +44,34 @@ def launch_setup(context, *args, **kwargs): def generate_launch_description(): # Declare arguments package_arg = DeclareLaunchArgument( - 'pkg_name', - default_value='my_robot_pkg', - description='Package containing the launch files' + "pkg_name", default_value="my_robot_pkg", description="Package containing the launch files" ) use_sim_arg = DeclareLaunchArgument( - 'use_sim_time', - default_value='true', - description='Use simulation clock' + "use_sim_time", default_value="true", description="Use simulation clock" ) # Include included_launch = IncludeLaunchDescription( - PythonLaunchDescriptionSource([ - LaunchConfiguration('pkg_name'), - '/launch/other_launch_file.py' - ]) + PythonLaunchDescriptionSource( + [LaunchConfiguration("pkg_name"), "/launch/other_launch_file.py"] + ) ) # Group with condition conditional_group = GroupAction( actions=[ Node( - package='demo_nodes_cpp', - executable='talker', - name='conditional_talker', - output='screen', - parameters=[{'use_sim_time': LaunchConfiguration('use_sim_time')}] + package="demo_nodes_cpp", + executable="talker", + name="conditional_talker", + output="screen", + parameters=[{"use_sim_time": LaunchConfiguration("use_sim_time")}], ) ], - condition=IfCondition(PythonExpression(["'", LaunchConfiguration('use_sim_time'), "' == 'true'"])), - namespace='sim_ns' + condition=IfCondition( + PythonExpression(["'", LaunchConfiguration("use_sim_time"), "' == 'true'"]) + ), + namespace="sim_ns", ) # OpaqueFunction @@ -77,32 +79,27 @@ def generate_launch_description(): # Composable Container + Nodes container = ComposableNodeContainer( - name='my_container', - namespace='', - package='rclcpp_components', - executable='component_container_mt', + name="my_container", + namespace="", + package="rclcpp_components", + executable="component_container_mt", composable_node_descriptions=[ ComposableNode( - package='demo_nodes_cpp', - plugin='demo_nodes_cpp::Talker', - name='talker_component', - parameters=[{'use_sim_time': LaunchConfiguration('use_sim_time')}] + package="demo_nodes_cpp", + plugin="demo_nodes_cpp::Talker", + name="talker_component", + parameters=[{"use_sim_time": LaunchConfiguration("use_sim_time")}], ), ComposableNode( - package='demo_nodes_cpp', - plugin='demo_nodes_cpp::Listener', - name='listener_component', - parameters=[{'use_sim_time': LaunchConfiguration('use_sim_time')}] - ) + package="demo_nodes_cpp", + plugin="demo_nodes_cpp::Listener", + name="listener_component", + parameters=[{"use_sim_time": LaunchConfiguration("use_sim_time")}], + ), ], - output='screen' + output="screen", ) - return LaunchDescription([ - package_arg, - use_sim_arg, - included_launch, - conditional_group, - opaque, - container - ]) + return LaunchDescription( + [package_arg, use_sim_arg, included_launch, conditional_group, opaque, container] + ) diff --git a/parser/includes/resolver.py b/parser/includes/resolver.py index 396ddbe..bcd4402 100644 --- a/parser/includes/resolver.py +++ b/parser/includes/resolver.py @@ -14,11 +14,15 @@ import os import warnings + from parser.context import ParseContext -from parser.resolution.resolution_engine import ResolutionEngine from parser.entrypoint.parser_runner import parse_launch_file +from parser.resolution.resolution_engine import ResolutionEngine -def resolve_included_launch_file(filename: str, parent_context: ParseContext, passed_arguments: list) -> dict: + +def resolve_included_launch_file( + filename: str, parent_context: ParseContext, passed_arguments: list +) -> dict: """ Parses the included launch file, applying passed arguments and merging introspection. """ @@ -37,6 +41,5 @@ def resolve_included_launch_file(filename: str, parent_context: ParseContext, pa if isinstance(pair, (list, tuple)) and len(pair) == 2: name, value = pair child_context.define_variable(name, value) - + return parse_launch_file(filename, child_context) - \ No newline at end of file diff --git a/parser/introspection/tracker.py b/parser/introspection/tracker.py index 58952bd..af61248 100644 --- a/parser/introspection/tracker.py +++ b/parser/introspection/tracker.py @@ -12,9 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -from collections import defaultdict from parser.parser.utils.common import compute_entity_key + class IntrospectionTracker: def __init__(self): # All declared arguments from DeclareLaunchArgument @@ -49,14 +49,14 @@ def track_launch_config_usage(self, name: str): Adds the name to the used set. """ self.used_launch_configs.add(name) - + def get_undeclared_launch_configs(self) -> set[str]: """ Returns all LaunchConfigurations used but not declared. Useful for validation and diagnostics. """ return self.used_launch_configs - set(self.declared_launch_args.keys()) - + # Environment Variables def track_environment_variable(self, name: str, metadata: dict): """ @@ -64,13 +64,13 @@ def track_environment_variable(self, name: str, metadata: dict): Adds the name to the used set. """ self.environment_variables[name] = metadata or {} - + def get_environment_variables(self) -> list: """ Returns all EnvironmentVariable. """ return list(self.environment_variables.values()) - + # Event Handler def add_event_handler(self, handler: str): """ @@ -83,7 +83,7 @@ def next_event_handler_index(self) -> int: Return the index of the next event handler """ return len(self.event_handlers) - + def register_entity(self, entity): """ Add entity to output only once (used for nodes, timers, loginfo, etc.) @@ -91,19 +91,16 @@ def register_entity(self, entity): key = compute_entity_key(entity) if key not in self.entities_by_key: self.entities_by_key[key] = entity - + def get_registered_entities(self): """ Get all the registered entities """ return list(self.entities_by_key.values()) - + # Python Expressions def track_python_expression(self, body: str, variables: list[str]): - self.symbolic_python_expressions.append({ - "body": body, - "variables": variables - }) + self.symbolic_python_expressions.append({"body": body, "variables": variables}) def get_python_expressions(self) -> list[dict]: - return self.symbolic_python_expressions \ No newline at end of file + return self.symbolic_python_expressions diff --git a/parser/parser/dispatcher.py b/parser/parser/dispatcher.py index e40241f..04af22e 100644 --- a/parser/parser/dispatcher.py +++ b/parser/parser/dispatcher.py @@ -13,13 +13,15 @@ # limitations under the License. import ast + +from parser.context import ParseContext from parser.parser.loader import register_builtin_handlers from parser.parser.registry import get_handler -from parser.context import ParseContext from parser.resolution.utils import get_func_name register_builtin_handlers() + def dispatch_call(node: ast.Call, context: ParseContext) -> dict: """ Dispatch a launch construct call to its registered handler. @@ -33,5 +35,5 @@ def dispatch_call(node: ast.Call, context: ParseContext) -> dict: if not handler: raise ValueError(f"Unrecognized launch construct: '{func_name}'") - - return handler(node, context) \ No newline at end of file + + return handler(node, context) diff --git a/parser/parser/handlers/command_handler.py b/parser/parser/handlers/command_handler.py index 0090bc6..e25a0ad 100644 --- a/parser/parser/handlers/command_handler.py +++ b/parser/parser/handlers/command_handler.py @@ -13,15 +13,14 @@ # limitations under the License. import ast + from parser.context import ParseContext from parser.parser.registry import register_handler from parser.resolution.utils import resolve_call_signature + @register_handler("Command", "launch.substitutions.Command") def handle_find_package_share(node: ast.Call, context: ParseContext) -> dict: args, _ = resolve_call_signature(node, context.engine) - return { - "type": "Command", - "command": args[0] - } \ No newline at end of file + return {"type": "Command", "command": args[0]} diff --git a/parser/parser/handlers/composable_node_container_handler.py b/parser/parser/handlers/composable_node_container_handler.py index 797d7a1..c7b1738 100644 --- a/parser/parser/handlers/composable_node_container_handler.py +++ b/parser/parser/handlers/composable_node_container_handler.py @@ -13,11 +13,13 @@ # limitations under the License. import ast + from parser.context import ParseContext from parser.parser.registry import register_handler from parser.parser.utils.common import flatten_once, group_entities_by_type from parser.resolution.utils import resolve_call_signature + @register_handler("ComposableNodeContainer", "launch_ros.actions.ComposableNodeContainer") def handle_composable_container(node: ast.Call, context: ParseContext) -> dict: args, kwargs = resolve_call_signature(node, context.engine) @@ -37,13 +39,10 @@ def handle_composable_container(node: ast.Call, context: ParseContext) -> dict: } for key, value in kwargs.items(): if key not in {"composable_node_descriptions", "name"}: - container_metadata[key] = value + container_metadata[key] = value # Register container and attach nodes context.register_composable_node_group(container_name, container_metadata) context.extend_composable_node_group(container_name, composable_nodes) - return { - "type": "ComposableNodeContainer", - "target_container": container_name - } + return {"type": "ComposableNodeContainer", "target_container": container_name} diff --git a/parser/parser/handlers/composable_node_handler.py b/parser/parser/handlers/composable_node_handler.py index b820044..ebec479 100644 --- a/parser/parser/handlers/composable_node_handler.py +++ b/parser/parser/handlers/composable_node_handler.py @@ -13,12 +13,14 @@ # limitations under the License. import ast + from parser.context import ParseContext from parser.parser.registry import register_handler from parser.resolution.utils import resolve_call_signature + @register_handler("ComposableNode", "launch_ros.descriptions.ComposableNode") def handle_composable_node(node: ast.Call, context: ParseContext) -> dict: args, kwargs = resolve_call_signature(node, context.engine) - return {"type": "ComposableNode", **kwargs} \ No newline at end of file + return {"type": "ComposableNode", **kwargs} diff --git a/parser/parser/handlers/condition_handler.py b/parser/parser/handlers/condition_handler.py index 8c468b4..f39ef41 100644 --- a/parser/parser/handlers/condition_handler.py +++ b/parser/parser/handlers/condition_handler.py @@ -12,21 +12,24 @@ # See the License for the specific language governing permissions and # limitations under the License. +import ast + from parser.context import ParseContext from parser.parser.postprocessing import simplify_launch_configurations from parser.parser.registry import register_handler from parser.resolution.utils import resolve_call_signature -import ast + @register_handler("IfCondition", "launch.conditions.IfCondition") def handle_if_condition(node: ast.Call, context: ParseContext) -> dict: args, kwargs = resolve_call_signature(node, context.engine) if args: kwargs["expression"] = args[0] - + expression = simplify_launch_configurations(kwargs["expression"]) return f"${{IfCondition:{expression}}}" + @register_handler("UnlessCondition", "launch.conditions.UnlessCondition") def handle_unless_condition(node: ast.Call, context: ParseContext) -> dict: args, kwargs = resolve_call_signature(node, context.engine) diff --git a/parser/parser/handlers/declare_launch_argument_handler.py b/parser/parser/handlers/declare_launch_argument_handler.py index 9747bcf..68ea796 100644 --- a/parser/parser/handlers/declare_launch_argument_handler.py +++ b/parser/parser/handlers/declare_launch_argument_handler.py @@ -13,9 +13,11 @@ # limitations under the License. import ast + from parser.context import ParseContext -from parser.resolution.utils import resolve_call_signature from parser.parser.registry import register_handler +from parser.resolution.utils import resolve_call_signature + @register_handler("DeclareLaunchArgument", "launch.actions.DeclareLaunchArgument") def handle_declare_launch_argument(node: ast.Call, context: ParseContext) -> dict: @@ -28,10 +30,10 @@ def handle_declare_launch_argument(node: ast.Call, context: ParseContext) -> dic # Positional fallback: first arg = name if "name" not in kwargs and args: kwargs["name"] = args[0] - + # Track for introspection arg_name = kwargs.get("name") if arg_name: context.introspection.track_launch_arg_declaration(arg_name, kwargs) - return {"type": "DeclareLaunchArgument", **kwargs} \ No newline at end of file + return {"type": "DeclareLaunchArgument", **kwargs} diff --git a/parser/parser/handlers/environment_variable_handler.py b/parser/parser/handlers/environment_variable_handler.py index 9dd1b6a..2bf9467 100644 --- a/parser/parser/handlers/environment_variable_handler.py +++ b/parser/parser/handlers/environment_variable_handler.py @@ -13,6 +13,7 @@ # limitations under the License. import ast + from parser.context import ParseContext from parser.parser.registry import register_handler from parser.resolution.utils import resolve_call_signature @@ -21,7 +22,7 @@ @register_handler("EnvironmentVariable", "launch.substitutions.EnvironmentVariable") def handle_environment_variable(node: ast.Call, context: ParseContext) -> dict: args, kwargs = resolve_call_signature(node, context.engine) - + # Positional fallback: first arg = name if "name" not in kwargs and args: kwargs["name"] = args[0] @@ -29,4 +30,4 @@ def handle_environment_variable(node: ast.Call, context: ParseContext) -> dict: # Track environment variable usage context.introspection.track_environment_variable(kwargs["name"], kwargs) - return {"type": "EnvironmentVariable", **kwargs} \ No newline at end of file + return {"type": "EnvironmentVariable", **kwargs} diff --git a/parser/parser/handlers/find_executable.py b/parser/parser/handlers/find_executable.py index 270122e..23c3f17 100644 --- a/parser/parser/handlers/find_executable.py +++ b/parser/parser/handlers/find_executable.py @@ -13,10 +13,12 @@ # limitations under the License. import ast + from parser.context import ParseContext from parser.parser.registry import register_handler from parser.resolution.utils import resolve_call_signature + @register_handler("FindExecutable", "launch.substitutions.FindExecutable") def handle_find_executable(node: ast.Call, context: ParseContext) -> dict: args, kwargs = resolve_call_signature(node, context.engine) @@ -25,7 +27,4 @@ def handle_find_executable(node: ast.Call, context: ParseContext) -> dict: if not name: raise ValueError("FindExecutable requires the name of the executable.") - return { - "type": "FindExecutable", - "name": name - } \ No newline at end of file + return {"type": "FindExecutable", "name": name} diff --git a/parser/parser/handlers/find_package_share.py b/parser/parser/handlers/find_package_share.py index c43fdd9..c1ae00b 100644 --- a/parser/parser/handlers/find_package_share.py +++ b/parser/parser/handlers/find_package_share.py @@ -13,17 +13,16 @@ # limitations under the License. import ast + from parser.context import ParseContext from parser.parser.registry import register_handler from parser.resolution.utils import resolve_call_signature + @register_handler("FindPackageShare", "launch.substitutions.FindPackageShare") def handle_find_package_share(node: ast.Call, context: ParseContext) -> dict: args, _ = resolve_call_signature(node, context.engine) if not args: raise ValueError("FindPackageShare requires a package name.") - return { - "type": "FindPackageShare", - "package": args[0] - } \ No newline at end of file + return {"type": "FindPackageShare", "package": args[0]} diff --git a/parser/parser/handlers/group_handler.py b/parser/parser/handlers/group_handler.py index 6c98278..940e16c 100644 --- a/parser/parser/handlers/group_handler.py +++ b/parser/parser/handlers/group_handler.py @@ -13,11 +13,13 @@ # limitations under the License. import ast + from parser.context import ParseContext from parser.parser.registry import register_handler from parser.parser.utils.common import flatten_once, group_entities_by_type from parser.resolution.utils import resolve_call_signature + @register_handler("GroupAction", "launch.actions.GroupAction") def handle_group_action(node: ast.Call, context: ParseContext) -> dict: """ @@ -39,7 +41,7 @@ def handle_group_action(node: ast.Call, context: ParseContext) -> dict: context.push_namespace(namespace) else: actions.append(item) - + grouped = group_entities_by_type(actions) if namespace: @@ -55,4 +57,3 @@ def handle_group_action(node: ast.Call, context: ParseContext) -> dict: result["namespace"] = namespace return result - \ No newline at end of file diff --git a/parser/parser/handlers/include_handler.py b/parser/parser/handlers/include_handler.py index c8fd9b0..a024047 100644 --- a/parser/parser/handlers/include_handler.py +++ b/parser/parser/handlers/include_handler.py @@ -13,12 +13,13 @@ # limitations under the License. import ast + from parser.context import ParseContext from parser.parser.postprocessing import simplify_launch_configurations from parser.parser.registry import register_handler -from parser.parser.utils.symbolic import is_symbolic from parser.resolution.utils import resolve_call_signature + @register_handler("IncludeLaunchDescription", "launch.actions.IncludeLaunchDescription") def handle_include(node: ast.Call, context: ParseContext) -> dict: """ @@ -34,7 +35,7 @@ def handle_include(node: ast.Call, context: ParseContext) -> dict: if not isinstance(launch_source, dict) or "filename" not in launch_source: raise ValueError("Could not resolve include path from launch_description_source") - + file_value = launch_source["filename"] # Flatten and stringify all parts @@ -43,7 +44,7 @@ def handle_include(node: ast.Call, context: ParseContext) -> dict: path = "".join(str(part) for part in file_value) else: path = str(file_value) - + # Load and parse included file # from parser.includes.resolver import resolve_included_launch_file # included_output = resolve_included_launch_file( @@ -56,10 +57,10 @@ def handle_include(node: ast.Call, context: ParseContext) -> dict: "type": "IncludeLaunchDescription", "launch_description_source": path, "launch_arguments": launch_args, - "included": {} + "included": {}, } if condition: result["condition"] = condition - return result \ No newline at end of file + return result diff --git a/parser/parser/handlers/launch_configuration_handler.py b/parser/parser/handlers/launch_configuration_handler.py index 9c25bc8..2833462 100644 --- a/parser/parser/handlers/launch_configuration_handler.py +++ b/parser/parser/handlers/launch_configuration_handler.py @@ -13,6 +13,7 @@ # limitations under the License. import ast + from parser.context import ParseContext from parser.parser.registry import register_handler from parser.resolution.utils import resolve_call_signature @@ -23,8 +24,8 @@ def handle_launch_config(node: ast.Call, context: ParseContext) -> dict: args, _ = resolve_call_signature(node, context.engine) if not args: raise ValueError("LaunchConfiguration must have a name.") - + name = args[0] context.introspection.track_launch_config_usage(name) - return {"type": "LaunchConfiguration", "name": name} \ No newline at end of file + return {"type": "LaunchConfiguration", "name": name} diff --git a/parser/parser/handlers/launch_description_handler.py b/parser/parser/handlers/launch_description_handler.py index f8f36a9..29c51ca 100644 --- a/parser/parser/handlers/launch_description_handler.py +++ b/parser/parser/handlers/launch_description_handler.py @@ -13,6 +13,7 @@ # limitations under the License. import ast + from parser.context import ParseContext from parser.parser.registry import register_handler from parser.parser.utils.common import flatten_once @@ -27,4 +28,4 @@ def handle_launch_description(node: ast.Call, context: ParseContext) -> dict: arg = args[0] return flatten_once(arg) if isinstance(arg, list) else [arg] - return [] \ No newline at end of file + return [] diff --git a/parser/parser/handlers/launch_sources.py b/parser/parser/handlers/launch_sources.py index f8a453e..8465c48 100644 --- a/parser/parser/handlers/launch_sources.py +++ b/parser/parser/handlers/launch_sources.py @@ -12,12 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -from parser.parser.registry import register_handler +import ast + from parser.context import ParseContext +from parser.parser.registry import register_handler from parser.resolution.utils import resolve_call_signature -import ast -@register_handler("PythonLaunchDescriptionSource", "launch.launch_description_sources.PythonLaunchDescriptionSource") + +@register_handler( + "PythonLaunchDescriptionSource", + "launch.launch_description_sources.PythonLaunchDescriptionSource", +) def handle_python_launch_source(node: ast.Call, context: ParseContext) -> dict: """ Handles PythonLaunchDescriptionSource("sublaunch.py") @@ -27,20 +32,16 @@ def handle_python_launch_source(node: ast.Call, context: ParseContext) -> dict: if not args: raise ValueError("PythonLaunchDescriptionSource must recieve a file path.") - + raw_path = args[0] - return { - "type": "PythonLaunchDescriptionSource", - "filename": raw_path - } + return {"type": "PythonLaunchDescriptionSource", "filename": raw_path} + @register_handler("ThisLaunchFileDir", "launch.substitutions.ThisLaunchFileDir") def handle_this_launch_file_dir(node: ast.Call, context: ParseContext) -> dict: """ Handles ThisLaunchFileDir() substitution. - Returns: {"type": "ThisLaunchFileDir"} + Returns: {"type": "ThisLaunchFileDir"} """ - return { - "type": "ThisLaunchFileDir" - } \ No newline at end of file + return {"type": "ThisLaunchFileDir"} diff --git a/parser/parser/handlers/load_composable_node_handler.py b/parser/parser/handlers/load_composable_node_handler.py index 1dab2ea..cd7fc73 100644 --- a/parser/parser/handlers/load_composable_node_handler.py +++ b/parser/parser/handlers/load_composable_node_handler.py @@ -13,11 +13,13 @@ # limitations under the License. import ast + from parser.context import ParseContext from parser.parser.registry import register_handler from parser.parser.utils.common import flatten_once, group_entities_by_type from parser.resolution.utils import resolve_call_signature + @register_handler("LoadComposableNodes", "launch_ros.actions.LoadComposableNodes") def handle_load_composable_nodes(node: ast.Call, context: ParseContext) -> dict: args, kwargs = resolve_call_signature(node, context.engine) @@ -37,7 +39,4 @@ def handle_load_composable_nodes(node: ast.Call, context: ParseContext) -> dict: context.register_composable_node_group(target_container, {"target_container": target_container}) context.extend_composable_node_group(target_container, composable_nodes) - return { - "type": "LoadComposableNodes", - "target_container": target_container - } \ No newline at end of file + return {"type": "LoadComposableNodes", "target_container": target_container} diff --git a/parser/parser/handlers/node_handler.py b/parser/parser/handlers/node_handler.py index c507dcc..33a9638 100644 --- a/parser/parser/handlers/node_handler.py +++ b/parser/parser/handlers/node_handler.py @@ -13,10 +13,12 @@ # limitations under the License. import ast + from parser.context import ParseContext from parser.parser.registry import register_handler from parser.resolution.utils import resolve_call_kwargs + @register_handler("Node", "launch_ros.actions.Node") def handle_node(node: ast.Call, context: ParseContext) -> dict: """ @@ -24,7 +26,7 @@ def handle_node(node: ast.Call, context: ParseContext) -> dict: Adds namespace context if not explicitly passed. """ kwargs = resolve_call_kwargs(node, context.engine) - + if "namespace" not in kwargs: ns = context.current_namespace() if ns: diff --git a/parser/parser/handlers/on_process_exit_handler.py b/parser/parser/handlers/on_process_exit_handler.py index 3b473db..14f5eda 100644 --- a/parser/parser/handlers/on_process_exit_handler.py +++ b/parser/parser/handlers/on_process_exit_handler.py @@ -12,11 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +import ast + from parser.context import ParseContext from parser.parser.registry import register_handler -from parser.parser.utils.common import compute_entity_key from parser.resolution.utils import resolve_call_signature -import ast + @register_handler("OnProcessExit", "launch_ros.handlers.OnProcessExit") def handle_on_process_exit(node: ast.Call, context: ParseContext): @@ -38,7 +39,7 @@ def handle_on_process_exit(node: ast.Call, context: ParseContext): for target in target_actions: target.setdefault("events", {}).setdefault("triggers", []).append(handler_ref) context.introspection.register_entity(target) - + for triggered in triggered_actions: triggered.setdefault("events", {}).setdefault("triggered_by", []).append(handler_ref) context.introspection.register_entity(triggered) diff --git a/parser/parser/handlers/on_process_start_handler.py b/parser/parser/handlers/on_process_start_handler.py index 07b79c8..cc09c31 100644 --- a/parser/parser/handlers/on_process_start_handler.py +++ b/parser/parser/handlers/on_process_start_handler.py @@ -12,10 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +import ast + from parser.context import ParseContext from parser.parser.registry import register_handler from parser.resolution.utils import resolve_call_signature -import ast + @register_handler("OnProcessStart", "launch_ros.handlers.OnProcessStart") def handle_on_process_start(node: ast.Call, context: ParseContext): @@ -37,7 +39,7 @@ def handle_on_process_start(node: ast.Call, context: ParseContext): for target in target_actions: target.setdefault("events", {}).setdefault("triggers", []).append(handler_ref) context.introspection.register_entity(target) - + for triggered in triggered_actions: triggered.setdefault("events", {}).setdefault("triggered_by", []).append(handler_ref) context.introspection.register_entity(triggered) diff --git a/parser/parser/handlers/opaque_function_handler.py b/parser/parser/handlers/opaque_function_handler.py index edf3ded..4ad1487 100644 --- a/parser/parser/handlers/opaque_function_handler.py +++ b/parser/parser/handlers/opaque_function_handler.py @@ -13,13 +13,15 @@ # limitations under the License. import ast + from parser.context import ParseContext from parser.parser.postprocessing import simplify_launch_configurations from parser.parser.registry import register_handler +from parser.parser.utils.ast_utils import extract_opaque_function from parser.parser.utils.common import group_entities_by_type from parser.resolution.resolution_engine import ResolutionEngine from parser.resolution.utils import bind_function_args, resolve_call_signature -from parser.parser.utils.ast_utils import extract_opaque_function + @register_handler("OpaqueFunction", "launch.actions.OpaqueFunction") def handle_opaque_function(node: ast.Call, context: ParseContext) -> dict: @@ -37,7 +39,7 @@ def handle_opaque_function(node: ast.Call, context: ParseContext) -> dict: raise ValueError(f"Function '{function_name}' not found in parsed context.") else: raise ValueError("OpaqueFunction 'function' must be a string or FunctionDef.") - + # Get full function signature param_parts = [arg.arg for arg in fn_def.args.args] if fn_def.args.vararg: @@ -45,14 +47,18 @@ def handle_opaque_function(node: ast.Call, context: ParseContext) -> dict: if fn_def.args.kwarg: param_parts.append(f"**{fn_def.args.kwarg.arg}") full_signature = f"{fn_def.name}({', '.join(param_parts)})" - + # Build symbolic context symbolic_context = ParseContext(context.introspection) symbolic_context.strategy = "symbolic" # Bind arguments and inject into symbolic context - user_args = simplify_launch_configurations(kwargs.get("args", []) if isinstance(kwargs.get("args"), list) else []) - user_kwargs = simplify_launch_configurations(kwargs.get("kwargs", []) if isinstance(kwargs.get("kwargs"), dict) else {}) + user_args = simplify_launch_configurations( + kwargs.get("args", []) if isinstance(kwargs.get("args"), list) else [] + ) + user_kwargs = simplify_launch_configurations( + kwargs.get("kwargs", []) if isinstance(kwargs.get("kwargs"), dict) else {} + ) param_binding = bind_function_args(fn_def, user_args, user_kwargs, True) for var_name, value in param_binding.items(): symbolic_context.define_variable(var_name, value) @@ -65,8 +71,4 @@ def handle_opaque_function(node: ast.Call, context: ParseContext) -> dict: result = extract_opaque_function(fn_def, context, symbolic_engine) grouped = group_entities_by_type(result) - return { - "type": "OpaqueFunction", - "name": full_signature, - "returns": grouped - } \ No newline at end of file + return {"type": "OpaqueFunction", "name": full_signature, "returns": grouped} diff --git a/parser/parser/handlers/parameter_file_handler.py b/parser/parser/handlers/parameter_file_handler.py index d80c9bd..dd65dd2 100644 --- a/parser/parser/handlers/parameter_file_handler.py +++ b/parser/parser/handlers/parameter_file_handler.py @@ -13,11 +13,13 @@ # limitations under the License. import ast + from parser.context import ParseContext from parser.parser.postprocessing import simplify_launch_configurations from parser.parser.registry import register_handler from parser.resolution.utils import resolve_call_signature + @register_handler("ParameterFile", "launch_ros.descriptions.ParameterFile") def handle_parameter_file(node: ast.Call, context: ParseContext) -> dict: args, kwargs = resolve_call_signature(node, context.engine) @@ -26,4 +28,4 @@ def handle_parameter_file(node: ast.Call, context: ParseContext) -> dict: allow_substs = kwargs.get("allow_substs", False) simplified_param_file = simplify_launch_configurations(param_file) - return f"${{ParameterFile:{simplified_param_file} allow_substs={allow_substs}}}" \ No newline at end of file + return f"${{ParameterFile:{simplified_param_file} allow_substs={allow_substs}}}" diff --git a/parser/parser/handlers/path_join.py b/parser/parser/handlers/path_join.py index 1027385..378962f 100644 --- a/parser/parser/handlers/path_join.py +++ b/parser/parser/handlers/path_join.py @@ -13,17 +13,16 @@ # limitations under the License. import ast + from parser.context import ParseContext from parser.parser.registry import register_handler from parser.resolution.utils import resolve_call_signature + @register_handler("PathJoinSubstitution", "launch.substitutions.PathJoinSubstitution") def handle_find_package_share(node: ast.Call, context: ParseContext) -> dict: args, _ = resolve_call_signature(node, context.engine) if not args or not isinstance(args[0], list): raise ValueError("PathJoinSubstitution requires a list as its first argument.") - return { - "type": "PathJoinSubstitution", - "parts": args[0] - } \ No newline at end of file + return {"type": "PathJoinSubstitution", "parts": args[0]} diff --git a/parser/parser/handlers/perform_handler.py b/parser/parser/handlers/perform_handler.py index 5a665d7..1456900 100644 --- a/parser/parser/handlers/perform_handler.py +++ b/parser/parser/handlers/perform_handler.py @@ -12,10 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -from parser.parser.registry import register_handler -from parser.resolution.utils import resolve_call_signature import ast +from parser.parser.registry import register_handler + + @register_handler("perform") def handle_perform(node: ast.Call, context): """ @@ -26,4 +27,4 @@ def handle_perform(node: ast.Call, context): raise ValueError("Expected method call for '.perform()'") base_call_node = node.func.value - return context.engine.resolve(base_call_node) \ No newline at end of file + return context.engine.resolve(base_call_node) diff --git a/parser/parser/handlers/push_ros_namespace_handler.py b/parser/parser/handlers/push_ros_namespace_handler.py index 17fb9e8..a9cd16c 100644 --- a/parser/parser/handlers/push_ros_namespace_handler.py +++ b/parser/parser/handlers/push_ros_namespace_handler.py @@ -12,17 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +import ast + from parser.context import ParseContext from parser.parser.registry import register_handler from parser.resolution.utils import resolve_call_signature -import ast + @register_handler("PushRosNamespace", "launch_ros.actions.PushRosNamespace") def handle_push_ros_namespace(node: ast.Call, context: ParseContext): args, kwargs = resolve_call_signature(node, context.engine) ns = args[0] if args else kwargs.get("namespace") - return { - "type": "PushRosNamespace", - "namespace": ns - } + return {"type": "PushRosNamespace", "namespace": ns} diff --git a/parser/parser/handlers/python_expression_handler.py b/parser/parser/handlers/python_expression_handler.py index 9de7894..02969e0 100644 --- a/parser/parser/handlers/python_expression_handler.py +++ b/parser/parser/handlers/python_expression_handler.py @@ -12,11 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +import ast + from parser.context import ParseContext from parser.parser.postprocessing import simplify_launch_configurations from parser.parser.registry import register_handler from parser.resolution.utils import resolve_call_signature -import ast + @register_handler("PythonExpression", "launch.substitutions.PythonExpression") def handle_if_condition(node: ast.Call, context: ParseContext) -> str: @@ -24,7 +26,7 @@ def handle_if_condition(node: ast.Call, context: ParseContext) -> str: if not args: raise ValueError("Python Expression requires at least one argument.") - + # Flatten and stringify all parts args = simplify_launch_configurations(args) if isinstance(args[0], list): diff --git a/parser/parser/handlers/register_event_handler.py b/parser/parser/handlers/register_event_handler.py index 6f64218..270a181 100644 --- a/parser/parser/handlers/register_event_handler.py +++ b/parser/parser/handlers/register_event_handler.py @@ -12,10 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +import ast + from parser.context import ParseContext from parser.parser.registry import register_handler from parser.resolution.utils import resolve_call_signature -import ast + @register_handler("RegisterEventHandler", "launch_ros.actions.RegisterEventHandler") def handle_register_event_handler(node: ast.Call, context: ParseContext): diff --git a/parser/parser/handlers/set_environment_variable_handler.py b/parser/parser/handlers/set_environment_variable_handler.py index 39d2175..4da0463 100644 --- a/parser/parser/handlers/set_environment_variable_handler.py +++ b/parser/parser/handlers/set_environment_variable_handler.py @@ -13,6 +13,7 @@ # limitations under the License. import ast + from parser.context import ParseContext from parser.parser.registry import register_handler from parser.resolution.utils import resolve_call_signature @@ -21,7 +22,7 @@ @register_handler("SetEnvironmentVariable", "launch.actions.SetEnvironmentVariable") def handle_set_environment_variable(node: ast.Call, context: ParseContext) -> dict: args, kwargs = resolve_call_signature(node, context.engine) - + # Handle both positional and keyword style if "name" not in kwargs and args: kwargs["name"] = args[0] @@ -31,4 +32,4 @@ def handle_set_environment_variable(node: ast.Call, context: ParseContext) -> di # Track environment variable usage context.introspection.track_environment_variable(kwargs["name"], kwargs) - return {"type": "EnvironmentVariable", **kwargs} \ No newline at end of file + return {"type": "EnvironmentVariable", **kwargs} diff --git a/parser/parser/handlers/set_parameter_handler.py b/parser/parser/handlers/set_parameter_handler.py index 7622e47..39dd4f9 100644 --- a/parser/parser/handlers/set_parameter_handler.py +++ b/parser/parser/handlers/set_parameter_handler.py @@ -13,10 +13,12 @@ # limitations under the License. import ast + from parser.context import ParseContext from parser.parser.registry import register_handler from parser.resolution.utils import resolve_call_signature + @register_handler("SetParameter", "launch.actions.SetParameter") def handle_set_parameter(node: ast.Call, context: ParseContext) -> dict: args, kwargs = resolve_call_signature(node, context.engine) @@ -24,8 +26,4 @@ def handle_set_parameter(node: ast.Call, context: ParseContext) -> dict: name = kwargs.get("name") value = kwargs.get("value") - return { - "type": "SetParameter", - "name": name, - "value": value - } \ No newline at end of file + return {"type": "SetParameter", "name": name, "value": value} diff --git a/parser/parser/handlers/timer_action_handler.py b/parser/parser/handlers/timer_action_handler.py index 3fae817..d10e972 100644 --- a/parser/parser/handlers/timer_action_handler.py +++ b/parser/parser/handlers/timer_action_handler.py @@ -13,11 +13,13 @@ # limitations under the License. import ast + from parser.context import ParseContext from parser.parser.registry import register_handler from parser.parser.utils.common import flatten_once, group_entities_by_type from parser.resolution.utils import resolve_call_signature + @register_handler("TimerAction", "launch.actions.TimerAction") def handle_timer_action(node: ast.Call, context: ParseContext) -> dict: """ @@ -32,8 +34,8 @@ def handle_timer_action(node: ast.Call, context: ParseContext) -> dict: actions = [] for item in resolved_flat: - actions.append(item) - + actions.append(item) + grouped = group_entities_by_type(actions) result = { @@ -43,4 +45,3 @@ def handle_timer_action(node: ast.Call, context: ParseContext) -> dict: } return result - \ No newline at end of file diff --git a/parser/parser/introspection_utils.py b/parser/parser/introspection_utils.py index 2b43d69..0aebec2 100644 --- a/parser/parser/introspection_utils.py +++ b/parser/parser/introspection_utils.py @@ -12,15 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. +import re from collections import defaultdict + from parser.parser.type_mapping import TYPE_KEY_MAP -import re LAUNCH_CONFIG_REGEX = re.compile(r"\${LaunchConfiguration:([a-zA-Z0-9_]+)}") ENVIRONMENT_VAR_REGEX = re.compile(r"\${EnvironmentVariable:([a-zA-Z0-9_]+)}") EVENT_HANDLER_REGEX = re.compile(r"\${EventHandler\[(\d+)\]:(\w+)}") PYTHON_VAR_REGEX = re.compile(r"\${var:([a-zA-Z_][a-zA-Z0-9_]*)}") + def collect_launch_config_usages(grouped: dict) -> list[dict]: """ Recursively walk the grouped data and return all LaunchConfiguration usages @@ -33,10 +35,7 @@ def walk(obj, path): for key, value in obj.items(): if key == "type" and obj.get("type") == "LaunchConfiguration": arg = obj.get("name") - usages.append({ - "argument": arg, - "path": path - }) + usages.append({"argument": arg, "path": path}) else: walk(value, f"{path}.{key}" if path else key) @@ -47,20 +46,18 @@ def walk(obj, path): elif isinstance(obj, tuple): for idx, item in enumerate(obj): walk(item, f"{path}[{idx}]") - + elif isinstance(obj, str): for match in LAUNCH_CONFIG_REGEX.finditer(obj): - usages.append({ - "argument": match.group(1), - "path": path - }) - + usages.append({"argument": match.group(1), "path": path}) + for top_key in TYPE_KEY_MAP.values(): for idx, entry in enumerate(grouped.get(top_key, [])): walk(entry, f"{top_key}[{idx}]") - + return usages + def collect_environment_variable_usages(grouped: dict) -> list[dict]: """ Recursively walk the grouped data and return all EnvironmentVariable usages @@ -73,10 +70,7 @@ def walk(obj, path): for key, value in obj.items(): if key == "type" and obj.get("type") == "EnvironmentVariable": arg = obj.get("name") - usages.append({ - "argument": arg, - "path": path - }) + usages.append({"argument": arg, "path": path}) else: walk(value, f"{path}.{key}" if path else key) @@ -87,20 +81,18 @@ def walk(obj, path): elif isinstance(obj, tuple): for idx, item in enumerate(obj): walk(item, f"{path}[{idx}]") - + elif isinstance(obj, str): for match in ENVIRONMENT_VAR_REGEX.finditer(obj): - usages.append({ - "argument": match.group(1), - "path": path - }) - + usages.append({"argument": match.group(1), "path": path}) + for top_key in TYPE_KEY_MAP.values(): for idx, entry in enumerate(grouped.get(top_key, [])): walk(entry, f"{top_key}[{idx}]") - + return usages + def collect_event_handler_usages(grouped: dict) -> list[dict]: """ Recursively walk the grouped data and return all event handler usages @@ -126,19 +118,20 @@ def walk(obj, path): usage_map[idx]["type"] = handler_type # Remove trailing index of triggers and triggered_by - clean_path = path.rsplit('[', 1)[0] if path.endswith(']') else path + clean_path = path.rsplit("[", 1)[0] if path.endswith("]") else path if ".triggers" in clean_path: usage_map[idx]["triggered_by"].append(clean_path) elif ".triggered_by" in clean_path: usage_map[idx]["triggers"].append(clean_path) - + for key in TYPE_KEY_MAP.values(): for idx, item in enumerate(grouped.get(key, [])): walk(item, f"{key}[{idx}]") - + return [v for v in usage_map.values() if v["triggered_by"] or v["triggers"]] + def collect_python_variable_usages(grouped: dict) -> list[dict]: """ Recursively walk the grouped data and return all ${var:...} usages @@ -150,7 +143,7 @@ def walk(obj, path): if isinstance(obj, dict): for key, value in obj.items(): walk(value, f"{path}.{key}" if path else key) - + elif isinstance(obj, list): for idx, item in enumerate(obj): walk(item, f"{path}[{idx}]") @@ -161,13 +154,10 @@ def walk(obj, path): elif isinstance(obj, str): for match in PYTHON_VAR_REGEX.finditer(obj): - usages.append({ - "variable": match.group(1), - "path": path - }) - + usages.append({"variable": match.group(1), "path": path}) + for top_key in TYPE_KEY_MAP.values(): for idx, entry in enumerate(grouped.get(top_key, [])): walk(entry, f"{top_key}[{idx}]") - + return usages diff --git a/parser/parser/loader.py b/parser/parser/loader.py index f0e2ed4..4e49c1d 100644 --- a/parser/parser/loader.py +++ b/parser/parser/loader.py @@ -13,8 +13,9 @@ # limitations under the License. import importlib -import pkgutil import os +import pkgutil + def register_builtin_handlers(): """ @@ -24,4 +25,4 @@ def register_builtin_handlers(): package_dir = os.path.dirname(parser.parser.handlers.__file__) for _, module_name, _ in pkgutil.iter_modules([package_dir]): - importlib.import_module(f"parser.parser.handlers.{module_name}") \ No newline at end of file + importlib.import_module(f"parser.parser.handlers.{module_name}") diff --git a/parser/parser/postprocessing.py b/parser/parser/postprocessing.py index aaaffb6..7418cec 100644 --- a/parser/parser/postprocessing.py +++ b/parser/parser/postprocessing.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. + def simplify_launch_configurations(obj): """ Recursively walk the parsed output and convert @@ -22,52 +23,68 @@ def simplify_launch_configurations(obj): if type_key in simplifier_registry: return simplifier_registry[type_key](obj) - + # Otherwise recursively simplify dictionary return {k: simplify_launch_configurations(v) for k, v in obj.items()} - + elif isinstance(obj, (list, tuple)): return [simplify_launch_configurations(i) for i in obj] return obj +def format_symbolic_part(part): + if isinstance(part, dict): + return simplify_launch_configurations(part) + elif isinstance(part, str): + return f"'{part}'" + else: + return str(part) + + def _simplify_launch_config(obj): name = obj.get("name") return f"${{LaunchConfiguration:{name}}}" + def _simplify_environment_variable(obj): name = obj.get("name") return f"${{EnvironmentVariable:{name}}}" + def _simplify_path_join(obj): - format_symbolic_part = lambda p: simplify_launch_configurations(p) if isinstance(p, dict) else f"'{p}'" if isinstance(p, str) else str(p) parts = ", ".join(format_symbolic_part(p) for p in obj.get("parts")) return f"${{PathJoinSubstitution:[{parts}]}}" + def _simplify_find_package(obj): package = obj.get("package") return f"${{FindPackageShare:{package}}}" + def _simplify_find_executable(obj): name = obj.get("name") return f"${{FindExecutable:{name}}}" + def _simplify_command(obj): - format_symbolic_part = lambda p: simplify_launch_configurations(p) if isinstance(p, dict) else f"'{p}'" if isinstance(p, str) else str(p) commands = ", ".join(format_symbolic_part(p) for p in obj.get("command")) return f"${{Command:[{commands}]}}" + def _simplify_this_launch_file_dir(obj): - return f"${{ThisLaunchFileDir}}" + return "${ThisLaunchFileDir}" + def _simplify_custom_handler(obj): type_name = obj.get("type_name") - format_symbolic_part = lambda p: simplify_launch_configurations(p) if isinstance(p, dict) else f"'{p}'" if isinstance(p, str) else str(p) - kwarg_strs = [f"{k}={format_symbolic_part(v)}" for k, v in obj.items() if k not in {"type", "type_name"}] + kwarg_strs = [ + f"{k}={format_symbolic_part(v)}" for k, v in obj.items() if k not in {"type", "type_name"} + ] kwargs = ", ".join(kwarg_strs) return f"${{CustomHandler:{type_name}({kwargs})}}" + # Dispatcher registry simplifier_registry = { "LaunchConfiguration": _simplify_launch_config, @@ -78,4 +95,4 @@ def _simplify_custom_handler(obj): "FindExecutable": _simplify_find_executable, "ThisLaunchFileDir": _simplify_this_launch_file_dir, "CustomHandler": _simplify_custom_handler, -} \ No newline at end of file +} diff --git a/parser/parser/registry.py b/parser/parser/registry.py index 1a246f7..ebbf753 100644 --- a/parser/parser/registry.py +++ b/parser/parser/registry.py @@ -12,36 +12,41 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Callable, Dict, Optional -import warnings import ast +import warnings +from typing import Callable, Dict, Optional from parser.context import ParseContext # Registry dictionary for known launch constructs -_HANDLER_REGISTRY: Dict[str, Callable[[ast.Call, 'ParseContext'], Optional[dict]]] = {} +_HANDLER_REGISTRY: Dict[str, Callable[[ast.Call, "ParseContext"], Optional[dict]]] = {} + def register_handler(*names: str): """ Decorator to register a handler for a given launch construct. Example: @register_handler("Node") registers a handler for launch_ros.actions.Node. """ - def decorator(func: Callable[[ast.Call, 'ParseContext'], Optional[dict]]): + + def decorator(func: Callable[[ast.Call, "ParseContext"], Optional[dict]]): for name in names: if name in _HANDLER_REGISTRY: warnings.warn(f"Overwriting existing handler for '{name}'") _HANDLER_REGISTRY[name] = func return func + return decorator -def get_handler(name: str) -> Optional[Callable[[ast.Call, 'ParseContext'], Optional[dict]]]: + +def get_handler(name: str) -> Optional[Callable[[ast.Call, "ParseContext"], Optional[dict]]]: """ Retrieve the handler for a given construct, or None if unregistered """ return _HANDLER_REGISTRY.get(name) + def all_registered() -> Dict[str, Callable]: """ Return the complete handler map (useful for debugging or listing). """ - return _HANDLER_REGISTRY.copy() \ No newline at end of file + return _HANDLER_REGISTRY.copy() diff --git a/parser/parser/type_mapping.py b/parser/parser/type_mapping.py index c7748ea..135c71d 100644 --- a/parser/parser/type_mapping.py +++ b/parser/parser/type_mapping.py @@ -22,5 +22,5 @@ "OpaqueFunction": "opaque_functions", "ComposableNode": "unattached_composable_nodes", "ComposableNodeContainer": "composable_nodes_container", - "CustomHandler": "custom_components" -} \ No newline at end of file + "CustomHandler": "custom_components", +} diff --git a/parser/parser/user_handler.py b/parser/parser/user_handler.py index 41e7970..537f402 100644 --- a/parser/parser/user_handler.py +++ b/parser/parser/user_handler.py @@ -12,15 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -from parser.parser.registry import register_handler import inspect +from parser.parser.registry import register_handler + + def register_user_handler(type_name: str): """ Decorator to register a user defined handler with validation. - Signature validation (must accept (node, context)) - Output validation (must return dict with 'type': "CustomHandler", 'type_name': ) """ + def decorator(fn): # Check signature sig = inspect.signature(fn) @@ -32,12 +35,12 @@ def decorator(fn): f"User handler for '{type_name}' must have signature: " f"({', '.join(expected_args)}), but got: ({', '.join(actual_args)})" ) - + # Wrap with validation def wrapper(node, context): result = fn(node, context) return result - + return register_handler(type_name)(wrapper) - - return decorator \ No newline at end of file + + return decorator diff --git a/parser/parser/utils/ast_utils.py b/parser/parser/utils/ast_utils.py index 48abe4b..8064dd5 100644 --- a/parser/parser/utils/ast_utils.py +++ b/parser/parser/utils/ast_utils.py @@ -13,8 +13,10 @@ # limitations under the License. import ast + from parser.context import ParseContext + def collect_function_defs(body: list[ast.stmt], context: ParseContext): """ Recursively collect all FunctionDef nodes inside a body list @@ -23,13 +25,14 @@ def collect_function_defs(body: list[ast.stmt], context: ParseContext): for stmt in body: if isinstance(stmt, ast.FunctionDef): context.define_function(stmt.name, stmt) - + # Recursively handle compound statements for attr in ("body", "orelse", "finalbody"): inner = getattr(stmt, attr, None) if isinstance(inner, list): collect_function_defs(inner, context) + def extract_opaque_function(fn_def: ast.FunctionDef, context, symbolic_engine): """ Execute the body of an OpaqueFunction symbolically and return parsed results. @@ -47,8 +50,8 @@ def extract_opaque_function(fn_def: ast.FunctionDef, context, symbolic_engine): elif isinstance(stmt, ast.Return): if stmt.value is None: return None - + result = symbolic_engine.resolve(stmt.value) break - - return result \ No newline at end of file + + return result diff --git a/parser/parser/utils/common.py b/parser/parser/utils/common.py index 6c5fa74..f6af484 100644 --- a/parser/parser/utils/common.py +++ b/parser/parser/utils/common.py @@ -14,6 +14,7 @@ from parser.parser.type_mapping import TYPE_KEY_MAP + def flatten_once(items): """ Flatten a list one level deep. @@ -27,6 +28,7 @@ def flatten_once(items): flattened.append(item) return flattened + def compute_entity_key(entity: dict) -> str: """ Generate a unique key for deduplication and tracking event links. @@ -38,6 +40,7 @@ def compute_entity_key(entity: dict) -> str: name = entity.get("name", "") return f"{t}::{pkg}::{exe}::{name}" + def group_entities_by_type(entities: list) -> dict: grouped = {} @@ -54,5 +57,5 @@ def group_entities_by_type(entities: list) -> dict: clean_item = {k: v for k, v in item.items() if k != "type"} grouped.setdefault(group_key, []).append(clean_item) - - return {k: v for k, v in grouped.items() if v} \ No newline at end of file + + return {k: v for k, v in grouped.items() if v} diff --git a/parser/parser/utils/symbolic.py b/parser/parser/utils/symbolic.py index 9c90475..742fc73 100644 --- a/parser/parser/utils/symbolic.py +++ b/parser/parser/utils/symbolic.py @@ -12,8 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. + def is_symbolic(value): """ Determines if a value is symbolic (non-concrete) object from the resolution engine. """ - return isinstance(value, dict) and "type" in value \ No newline at end of file + return isinstance(value, dict) and "type" in value diff --git a/parser/plugin_loader.py b/parser/plugin_loader.py index cbdc516..3e6fbaf 100644 --- a/parser/plugin_loader.py +++ b/parser/plugin_loader.py @@ -13,16 +13,17 @@ # limitations under the License. import importlib.util -import sys import os +import sys + def load_user_handlers_from_directory(base_dir): if not os.path.isdir(base_dir): print(f"Plugin directory '{base_dir}' not found.") return - + for file in os.listdir(base_dir): - if file.endswith('.py'): + if file.endswith(".py"): path = os.path.join(base_dir, file) spec = importlib.util.spec_from_file_location(f"user_plugin_{file}", path) if spec and spec.loader: @@ -31,4 +32,4 @@ def load_user_handlers_from_directory(base_dir): spec.loader.exec_module(module) print(f"[LaunchMap] Loaded plugin: {file}", file=sys.stderr) except Exception as e: - print(f"[LaunchMap] Failed to load plugin {file}: {e}", file=sys.stderr) \ No newline at end of file + print(f"[LaunchMap] Failed to load plugin {file}: {e}", file=sys.stderr) diff --git a/parser/pyproject.toml b/parser/pyproject.toml new file mode 100644 index 0000000..2442b11 --- /dev/null +++ b/parser/pyproject.toml @@ -0,0 +1,10 @@ +[tool.ruff] +line-length = 100 +target-version = "py310" + +lint.extend-select = ["E", "F", "I"] +lint.ignore = [] + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" \ No newline at end of file diff --git a/requirements.txt b/parser/requirements.txt similarity index 87% rename from requirements.txt rename to parser/requirements.txt index c1ef07b..97c8ca3 100644 --- a/requirements.txt +++ b/parser/requirements.txt @@ -4,3 +4,4 @@ pluggy==1.6.0 Pygments==2.19.1 pytest==8.4.0 PyYAML==6.0.2 +ruff==0.12.8 diff --git a/parser/resolution/loader.py b/parser/resolution/loader.py index 999ad06..9c6cb0d 100644 --- a/parser/resolution/loader.py +++ b/parser/resolution/loader.py @@ -13,8 +13,9 @@ # limitations under the License. import importlib -import pkgutil import os +import pkgutil + def register_builtin_resolvers(): """ @@ -24,4 +25,4 @@ def register_builtin_resolvers(): package_dir = os.path.dirname(parser.resolution.resolvers.__file__) for _, module_name, _ in pkgutil.iter_modules([package_dir]): - importlib.import_module(f"parser.resolution.resolvers.{module_name}") \ No newline at end of file + importlib.import_module(f"parser.resolution.resolvers.{module_name}") diff --git a/parser/resolution/resolution_engine.py b/parser/resolution/resolution_engine.py index 3ecc9a7..af1f526 100644 --- a/parser/resolution/resolution_engine.py +++ b/parser/resolution/resolution_engine.py @@ -13,10 +13,12 @@ # limitations under the License. import ast + from parser.context import ParseContext from parser.resolution.loader import register_builtin_resolvers from parser.resolution.utils import try_all_resolvers + class ResolutionEngine: def __init__(self, context: ParseContext): register_builtin_resolvers() @@ -25,4 +27,3 @@ def __init__(self, context: ParseContext): def resolve(self, node: ast.AST): return try_all_resolvers(node, self) - \ No newline at end of file diff --git a/parser/resolution/resolution_registry.py b/parser/resolution/resolution_registry.py index 45689df..a5de94c 100644 --- a/parser/resolution/resolution_registry.py +++ b/parser/resolution/resolution_registry.py @@ -12,26 +12,31 @@ # See the License for the specific language governing permissions and # limitations under the License. +import ast from typing import Callable, Dict, List, Tuple, Type + from parser.resolution.resolution_engine import ResolutionEngine -import ast # (priority, resolver_function) _RESOLVERS: Dict[Type[ast.AST], List[Tuple[int, Callable]]] = {} + def register_resolver(node_type: Type[ast.AST], *, priority: int = 0): """ Register a resolver for a given AST node type with an optional priority. Higher priority resolvers are tried first. """ - def decorator(func: Callable[[ast.AST, 'ResolutionEngine'], any]): + + def decorator(func: Callable[[ast.AST, "ResolutionEngine"], any]): _RESOLVERS.setdefault(node_type, []).append((priority, func)) _RESOLVERS[node_type].sort(key=lambda pair: -pair[0]) return func + return decorator + def get_resolvers(node_type: Type[ast.AST]) -> List[Callable]: """ Return resolvers sorted by priority (high to low) """ - return [f for _, f in _RESOLVERS.get(node_type, [])] \ No newline at end of file + return [f for _, f in _RESOLVERS.get(node_type, [])] diff --git a/parser/resolution/resolvers/assign.py b/parser/resolution/resolvers/assign.py index ff32e38..2e24fa4 100644 --- a/parser/resolution/resolvers/assign.py +++ b/parser/resolution/resolvers/assign.py @@ -12,13 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -from parser.resolution.resolution_registry import register_resolver import ast +from parser.resolution.resolution_registry import register_resolver + + @register_resolver(ast.Assign) def resolve_assign(node: ast.Assign, engine): value = engine.resolve(node.value) for target in node.targets: if isinstance(target, ast.Name): engine.context.define_variable(target.id, value) - return None \ No newline at end of file + return None diff --git a/parser/resolution/resolvers/attribute.py b/parser/resolution/resolvers/attribute.py index b94698b..a414252 100644 --- a/parser/resolution/resolvers/attribute.py +++ b/parser/resolution/resolvers/attribute.py @@ -12,9 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +import ast import re + from parser.resolution.resolution_registry import register_resolver -import ast + @register_resolver(ast.Attribute) def resolve_attribute(node: ast.Attribute, engine): @@ -23,27 +25,31 @@ def resolve_attribute(node: ast.Attribute, engine): else: return _resolve_normal_attribute(node, engine) + def _resolve_symbolic_attribute(node: ast.Attribute, engine): base = engine.resolve(node.value) if isinstance(base, str) and re.search(r"\$\{[^}]+\}", base): return f"{base}.{node.attr}" - + try: return getattr(base, node.attr) except AttributeError: raise ValueError(f"Object of type {type(base).__name__} has no attribute '{node.attr}'") + def _resolve_normal_attribute(node: ast.Attribute, engine): base_object = engine.resolve(node.value) if base_object is None: raise ValueError(f"Cannot access attribute '{node.attr}' on None") - + try: return getattr(base_object, node.attr) except AttributeError: - raise ValueError(f"Object of type {type(base_object).__name__} has no attribute '{node.attr}'") + raise ValueError( + f"Object of type {type(base_object).__name__} has no attribute '{node.attr}'" + ) def get_attr_chain(attr_node: ast.Attribute) -> str: @@ -54,4 +60,3 @@ def get_attr_chain(attr_node: ast.Attribute) -> str: if isinstance(attr_node, ast.Name): parts.insert(0, attr_node.id) return ".".join(parts) - \ No newline at end of file diff --git a/parser/resolution/resolvers/binop.py b/parser/resolution/resolvers/binop.py index d12fd84..8809f0c 100644 --- a/parser/resolution/resolvers/binop.py +++ b/parser/resolution/resolvers/binop.py @@ -12,13 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +import ast + from parser.resolution.resolution_registry import register_resolver from parser.resolution.utils import resolve_python_expression -import ast + @register_resolver(ast.BinOp) def resolve_binop(node: ast.BinOp, engine): left = engine.resolve(node.left) right = engine.resolve(node.right) return resolve_python_expression(left, node.op, right) - \ No newline at end of file diff --git a/parser/resolution/resolvers/boolop.py b/parser/resolution/resolvers/boolop.py index 4a9dffa..433cc3b 100644 --- a/parser/resolution/resolvers/boolop.py +++ b/parser/resolution/resolvers/boolop.py @@ -12,9 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -from parser.resolution.resolution_registry import register_resolver import ast +from parser.resolution.resolution_registry import register_resolver + + @register_resolver(ast.BoolOp) def resolve_boolop(node: ast.BoolOp, engine): values = [engine.resolve(v) for v in node.values] diff --git a/parser/resolution/resolvers/call.py b/parser/resolution/resolvers/call.py index c28a04e..4c6657c 100644 --- a/parser/resolution/resolvers/call.py +++ b/parser/resolution/resolvers/call.py @@ -12,11 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +import ast import warnings + +from parser.parser.dispatcher import dispatch_call from parser.parser.postprocessing import simplify_launch_configurations from parser.resolution.resolution_registry import register_resolver -from parser.parser.dispatcher import dispatch_call -import ast + @register_resolver(ast.Call, priority=0) def resolve_call(node: ast.Call, engine): @@ -25,6 +27,7 @@ def resolve_call(node: ast.Call, engine): else: return _resolve_normal_call(node, engine) + def _resolve_symbolic_call(node: ast.Call, engine): try: # Try launching launch_ros contruct @@ -52,10 +55,11 @@ def _resolve_symbolic_call(node: ast.Call, engine): arg_strs = [repr(a) for a in args] + [f"{k}={repr(v)}" for k, v in kwargs.items()] return f"{func}({', '.join(arg_strs)})" - + except Exception as e: raise ValueError(f"Unable to symbolically resolve call: {ast.dump(node)} -> {e}") + def _resolve_normal_call(node: ast.Call, engine): try: # Try launching launch_ros contruct @@ -78,6 +82,5 @@ def _resolve_normal_call(node: ast.Call, engine): if isinstance(result, type({}.items())): result = dict(result) return result - + raise ValueError(f"Cannot resolve non-callable: {func}") - \ No newline at end of file diff --git a/parser/resolution/resolvers/compare.py b/parser/resolution/resolvers/compare.py index 0ca92b0..34bbe3b 100644 --- a/parser/resolution/resolvers/compare.py +++ b/parser/resolution/resolvers/compare.py @@ -13,6 +13,7 @@ # limitations under the License. import ast + from parser.resolution.resolution_registry import register_resolver # Mapping from AST operator to symbolic string representation @@ -29,6 +30,7 @@ ast.IsNot: "is not", } + @register_resolver(ast.Compare) def resolve_compare(node: ast.Compare, engine): left = engine.resolve(node.left) @@ -50,4 +52,4 @@ def resolve_compare(node: ast.Compare, engine): comparisons.append(expr) left = right - return " and ".join(comparisons) if len(comparisons) > 1 else comparisons[0] \ No newline at end of file + return " and ".join(comparisons) if len(comparisons) > 1 else comparisons[0] diff --git a/parser/resolution/resolvers/constant.py b/parser/resolution/resolvers/constant.py index 265e5bf..a2f4f14 100644 --- a/parser/resolution/resolvers/constant.py +++ b/parser/resolution/resolvers/constant.py @@ -12,9 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -from parser.resolution.resolution_registry import register_resolver import ast +from parser.resolution.resolution_registry import register_resolver + + @register_resolver(ast.Constant) def resolve_constant(node: ast.Constant, engine): - return node.value \ No newline at end of file + return node.value diff --git a/parser/resolution/resolvers/dict.py b/parser/resolution/resolvers/dict.py index 8a097b4..03fcde0 100644 --- a/parser/resolution/resolvers/dict.py +++ b/parser/resolution/resolvers/dict.py @@ -12,12 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -from parser.resolution.resolution_registry import register_resolver import ast +from parser.resolution.resolution_registry import register_resolver + + @register_resolver(ast.Dict) def resolve_dict(node: ast.Dict, engine): - return { - engine.resolve(k): engine.resolve(v) - for k, v in zip(node.keys, node.values) - } \ No newline at end of file + return {engine.resolve(k): engine.resolve(v) for k, v in zip(node.keys, node.values)} diff --git a/parser/resolution/resolvers/expression.py b/parser/resolution/resolvers/expression.py index 9e31b3d..79472b4 100644 --- a/parser/resolution/resolvers/expression.py +++ b/parser/resolution/resolvers/expression.py @@ -12,9 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -from parser.resolution.resolution_registry import register_resolver import ast +from parser.resolution.resolution_registry import register_resolver + + @register_resolver(ast.Expr) def resolve_expression(node: ast.Expr, engine): - return engine.resolve(node.value) \ No newline at end of file + return engine.resolve(node.value) diff --git a/parser/resolution/resolvers/functiondef.py b/parser/resolution/resolvers/functiondef.py index c0f5b9a..f29944d 100644 --- a/parser/resolution/resolvers/functiondef.py +++ b/parser/resolution/resolvers/functiondef.py @@ -13,10 +13,12 @@ # limitations under the License. import ast + from parser.resolution.resolution_registry import register_resolver + @register_resolver(ast.FunctionDef) def resolve_function_def(node: ast.FunctionDef, engine): # Register in context engine.context.functions[node.name] = node - return None \ No newline at end of file + return None diff --git a/parser/resolution/resolvers/functions.py b/parser/resolution/resolvers/functions.py index c2f0a10..22c2044 100644 --- a/parser/resolution/resolvers/functions.py +++ b/parser/resolution/resolvers/functions.py @@ -12,13 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -from parser.resolution.resolution_engine import ResolutionEngine -from parser.resolution.resolution_registry import register_resolver -import copy import ast +from parser.resolution.resolution_engine import ResolutionEngine +from parser.resolution.resolution_registry import register_resolver from parser.resolution.utils import bind_function_args + @register_resolver(ast.Call, priority=5) def resolve_user_function(node: ast.Call, engine): # Resolve function name @@ -29,7 +29,7 @@ def resolve_user_function(node: ast.Call, engine): fn_def = engine.context.lookup_function(func_name) else: return None - + # Bind parameters resolved_args = [engine.resolve(arg) for arg in node.args] resolved_kwargs = {kw.arg: engine.resolve(kw.value) for kw in node.keywords} @@ -49,5 +49,5 @@ def resolve_user_function(node: ast.Call, engine): return sub_engine.resolve(stmt.value) else: sub_engine.resolve(stmt.value) - - return "Handled" \ No newline at end of file + + return "Handled" diff --git a/parser/resolution/resolvers/get_package_share_directory.py b/parser/resolution/resolvers/get_package_share_directory.py index cfa1f6b..40649d6 100644 --- a/parser/resolution/resolvers/get_package_share_directory.py +++ b/parser/resolution/resolvers/get_package_share_directory.py @@ -13,17 +13,19 @@ # limitations under the License. import ast + from parser.parser.postprocessing import simplify_launch_configurations from parser.resolution.resolution_registry import register_resolver + @register_resolver(ast.Call, priority=10) def resolve_get_package_share_directory(node: ast.Call, engine): func_name = engine.context.get_func_name(node.func) if func_name != "get_package_share_directory": return None - + if len(node.args) != 1: raise ValueError("get_package_share_directory expects 1 argument") - + arg = engine.resolve(node.args[0]) return f"${{get_package_share_directory:{simplify_launch_configurations(arg)}}}" diff --git a/parser/resolution/resolvers/ifelse.py b/parser/resolution/resolvers/ifelse.py index 774aafe..6996d48 100644 --- a/parser/resolution/resolvers/ifelse.py +++ b/parser/resolution/resolvers/ifelse.py @@ -13,8 +13,10 @@ # limitations under the License. import ast -from parser.resolution.utils import collect_assigned_variable_names + from parser.resolution.resolution_registry import register_resolver +from parser.resolution.utils import collect_assigned_variable_names + @register_resolver(ast.If) def resolve_if_expression(node: ast.If, engine): @@ -25,7 +27,7 @@ def resolve_if_expression(node: ast.If, engine): # Extract code as string code_str = ast.unparse(node) - + # Collect all variables assigned in any branch assigned_vars = set() @@ -42,9 +44,9 @@ def collect_from_block(block): else: collect_from_block(orelse) break - + # Track variables symbolic_vars = [f"${{var:{v}}}" for v in sorted(assigned_vars)] context.introspection.track_python_expression(code_str, symbolic_vars) - return None \ No newline at end of file + return None diff --git a/parser/resolution/resolvers/joined_str.py b/parser/resolution/resolvers/joined_str.py index 20a503d..293dde8 100644 --- a/parser/resolution/resolvers/joined_str.py +++ b/parser/resolution/resolvers/joined_str.py @@ -13,8 +13,10 @@ # limitations under the License. import ast + from parser.resolution.resolution_registry import register_resolver + @register_resolver(ast.JoinedStr) def resolve_function_def(node: ast.FunctionDef, engine): parts = [] @@ -26,5 +28,5 @@ def resolve_function_def(node: ast.FunctionDef, engine): parts.append(str(value.value)) else: parts.append(f" str: """ Reconstructs the fully qualified name from an AST function call, @@ -58,14 +60,17 @@ def get_func_name(func_node: ast.expr) -> str: return ".".join(parts) raise TypeError(f"Unsupported function node type: {type(func_node).__name__}") + def resolve_call_kwargs(node: ast.Call, engine): return {kw.arg: engine.resolve(kw.value) for kw in node.keywords} + def resolve_call_signature(node: ast.Call, engine): args = [engine.resolve(arg) for arg in node.args] kwargs = {kw.arg: engine.resolve(kw.value) for kw in node.keywords} return args, kwargs + def try_all_resolvers(node: ast.AST, engine) -> object: """ Attempts to resolve the given AST node using all registered resolvers @@ -81,13 +86,16 @@ def try_all_resolvers(node: ast.AST, engine) -> object: return result if not resolvers: - source = getattr(node, 'lineno', '?') + source = getattr(node, "lineno", "?") node_type = type(node).__name__ warnings.warn(f"Unhandled AST node ({node_type}) at line {source}: {ast.dump(node)}") - + return None -def bind_function_args(fn_def: ast.FunctionDef, args: list, kwargs: dict, exclude_first: bool = False) -> dict: + +def bind_function_args( + fn_def: ast.FunctionDef, args: list, kwargs: dict, exclude_first: bool = False +) -> dict: """ Bind args and kwargs to the function definition's parameters. Returns a mapping from parameter names to actual passed value. @@ -106,12 +114,12 @@ def bind_function_args(fn_def: ast.FunctionDef, args: list, kwargs: dict, exclud binding[name] = value # Remaining positional args (*args) - remaining_args = args[len(param_names):] + remaining_args = args[len(param_names) :] if vararg_name: binding[vararg_name] = remaining_args - + # Named kwargs that match a param name - for name in param_names[len(args):]: + for name in param_names[len(args) :]: if name in kwargs: binding[name] = kwargs[name] @@ -123,6 +131,7 @@ def bind_function_args(fn_def: ast.FunctionDef, args: list, kwargs: dict, exclud return binding + def collect_assigned_variable_names(stmt: ast.stmt) -> set[str]: """ Collect all variable names that are assigned in a statement. diff --git a/parser/tests/output_helper_script.py b/parser/tests/output_helper_script.py index 5ead862..f071448 100644 --- a/parser/tests/output_helper_script.py +++ b/parser/tests/output_helper_script.py @@ -12,8 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os import json +import os + from parser.entrypoint.user_interface import parse_and_format_launch_file from parser.plugin_loader import load_user_handlers_from_directory @@ -30,6 +31,6 @@ output_path = os.path.join(OUTPUT_DIR, f"{fname}.json") with open(output_path, "w") as f: - json.dump(result, f, indent = 2) - - print(f"โœ… Snapshot created: {output_path}") \ No newline at end of file + json.dump(result, f, indent=2) + + print(f"โœ… Snapshot created: {output_path}") diff --git a/parser/tests/real_cases/launch_files/custom_handlers/launch_config_as_bool_handler.py b/parser/tests/real_cases/launch_files/custom_handlers/launch_config_as_bool_handler.py index d864ef2..2b1fb64 100644 --- a/parser/tests/real_cases/launch_files/custom_handlers/launch_config_as_bool_handler.py +++ b/parser/tests/real_cases/launch_files/custom_handlers/launch_config_as_bool_handler.py @@ -13,6 +13,7 @@ # limitations under the License. import ast + from parser.context import ParseContext from parser.parser.registry import register_handler from parser.resolution.utils import resolve_call_signature @@ -23,8 +24,8 @@ def handle_launch_config(node: ast.Call, context: ParseContext) -> dict: args, _ = resolve_call_signature(node, context.engine) if not args: raise ValueError("LaunchConfiguration must have a name.") - + name = args[0] context.introspection.track_launch_config_usage(name) - return {"type": "LaunchConfiguration", "name": name, "as_bool": True} \ No newline at end of file + return {"type": "LaunchConfiguration", "name": name, "as_bool": True} diff --git a/parser/tests/real_cases/launch_files/custom_handlers/rewritten_yaml_handler.py b/parser/tests/real_cases/launch_files/custom_handlers/rewritten_yaml_handler.py index f1956f5..ac09e4d 100644 --- a/parser/tests/real_cases/launch_files/custom_handlers/rewritten_yaml_handler.py +++ b/parser/tests/real_cases/launch_files/custom_handlers/rewritten_yaml_handler.py @@ -13,8 +13,9 @@ # limitations under the License. from parser.parser.postprocessing import simplify_launch_configurations -from parser.resolution.utils import resolve_call_kwargs from parser.parser.user_handler import register_user_handler +from parser.resolution.utils import resolve_call_kwargs + @register_user_handler("RewrittenYaml") def handle_rewritten_yaml(node, context): @@ -22,9 +23,7 @@ def handle_rewritten_yaml(node, context): Handler for RewrittenYaml """ kwargs = resolve_call_kwargs(node, context.engine) - - return simplify_launch_configurations({ - "type": "CustomHandler", - "type_name": "RewrittenYaml", - **kwargs - }) \ No newline at end of file + + return simplify_launch_configurations( + {"type": "CustomHandler", "type_name": "RewrittenYaml", **kwargs} + ) diff --git a/parser/tests/real_cases/launch_files/launch_file_1.py b/parser/tests/real_cases/launch_files/launch_file_1.py index d5b0764..4d8d89f 100644 --- a/parser/tests/real_cases/launch_files/launch_file_1.py +++ b/parser/tests/real_cases/launch_files/launch_file_1.py @@ -12,10 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os from launch import LaunchDescription -from launch.actions import DeclareLaunchArgument, OpaqueFunction, IncludeLaunchDescription +from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription, OpaqueFunction from launch.conditions import IfCondition from launch.launch_description_sources import PythonLaunchDescriptionSource from launch.substitutions import LaunchConfiguration, PathJoinSubstitution @@ -53,7 +52,7 @@ def launch_setup(context, *args, **kwargs): ) return [ - rviz_node, + rviz_node, robot_launch, ] @@ -62,12 +61,8 @@ def generate_launch_description(): declared_arguments = [] def add_launch_arg(name: str, default_value: str = None): - declared_arguments.append( - DeclareLaunchArgument(name, default_value=default_value) - ) + declared_arguments.append(DeclareLaunchArgument(name, default_value=default_value)) add_launch_arg("enable_rviz", "False") - return LaunchDescription( - [*declared_arguments, OpaqueFunction(function=launch_setup)] - ) + return LaunchDescription([*declared_arguments, OpaqueFunction(function=launch_setup)]) diff --git a/parser/tests/real_cases/launch_files/launch_file_2.py b/parser/tests/real_cases/launch_files/launch_file_2.py index ee61708..71bcc13 100644 --- a/parser/tests/real_cases/launch_files/launch_file_2.py +++ b/parser/tests/real_cases/launch_files/launch_file_2.py @@ -47,9 +47,7 @@ def generate_launch_description(): def add_launch_arg(name: str, default_value: str = None, description: str = ""): declared_arguments.append( - DeclareLaunchArgument( - name, default_value=default_value, description=description - ) + DeclareLaunchArgument(name, default_value=default_value, description=description) ) add_launch_arg( @@ -58,6 +56,4 @@ def add_launch_arg(name: str, default_value: str = None, description: str = ""): description="Path to the driver parameter YAML file", ) - return LaunchDescription( - [*declared_arguments, OpaqueFunction(function=launch_setup)] - ) + return LaunchDescription([*declared_arguments, OpaqueFunction(function=launch_setup)]) diff --git a/parser/tests/real_cases/launch_files/launch_file_3.py b/parser/tests/real_cases/launch_files/launch_file_3.py index 7a22dbd..1614d60 100644 --- a/parser/tests/real_cases/launch_files/launch_file_3.py +++ b/parser/tests/real_cases/launch_files/launch_file_3.py @@ -13,10 +13,9 @@ # limitations under the License. from launch import LaunchDescription -from launch.actions import DeclareLaunchArgument, OpaqueFunction, IncludeLaunchDescription +from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription, OpaqueFunction from launch.launch_description_sources import PythonLaunchDescriptionSource -from launch.substitutions import LaunchConfiguration, PathJoinSubstitution -from launch_ros.actions import Node +from launch.substitutions import PathJoinSubstitution from launch_ros.substitutions import FindPackageShare @@ -42,25 +41,16 @@ def launch_setup(context, *args, **kwargs): ) ] ), - launch_arguments={ - "driver1_param_file": driver1_param_path - }.items(), + launch_arguments={"driver1_param_file": driver1_param_path}.items(), ) - return [ - driver1_launch - ] + return [driver1_launch] def generate_launch_description(): declared_arguments = [] def add_launch_arg(name: str, default_value: str = None): - declared_arguments.append( - DeclareLaunchArgument(name, default_value=default_value) - ) - - return LaunchDescription( - [*declared_arguments, OpaqueFunction(function=launch_setup)] - ) + declared_arguments.append(DeclareLaunchArgument(name, default_value=default_value)) + return LaunchDescription([*declared_arguments, OpaqueFunction(function=launch_setup)]) diff --git a/parser/tests/real_cases/launch_files/launch_file_4.py b/parser/tests/real_cases/launch_files/launch_file_4.py index 2ee9b41..0caa7a5 100644 --- a/parser/tests/real_cases/launch_files/launch_file_4.py +++ b/parser/tests/real_cases/launch_files/launch_file_4.py @@ -13,55 +13,58 @@ # limitations under the License. import os + +from ament_index_python.packages import get_package_share_directory from launch import LaunchDescription from launch.actions import DeclareLaunchArgument, OpaqueFunction from launch.substitutions import Command, LaunchConfiguration from launch_ros.actions import Node -from ament_index_python.packages import get_package_share_directory + def launch_setup(context, *args, **kwargs): - package_name = LaunchConfiguration('package_name').perform(context) - urdf_file = LaunchConfiguration('urdf_file').perform(context) - use_sim_time_str = LaunchConfiguration('use_sim_time').perform(context) + package_name = LaunchConfiguration("package_name").perform(context) + urdf_file = LaunchConfiguration("urdf_file").perform(context) + use_sim_time_str = LaunchConfiguration("use_sim_time").perform(context) package_directory = get_package_share_directory(package_name) - robot_desc_path = os.path.join(package_directory, 'urdf', urdf_file) - use_sim_time = use_sim_time_str.lower() in ['true', '1', 'yes'] + robot_desc_path = os.path.join(package_directory, "urdf", urdf_file) + use_sim_time = use_sim_time_str.lower() in ["true", "1", "yes"] params = { - 'use_sim_time': use_sim_time, - 'robot_description': Command(['xacro ', robot_desc_path]) + "use_sim_time": use_sim_time, + "robot_description": Command(["xacro ", robot_desc_path]), } node_robot_state_publisher = Node( - package='robot_state_publisher', - executable='robot_state_publisher', - output='screen', - parameters=[params] + package="robot_state_publisher", + executable="robot_state_publisher", + output="screen", + parameters=[params], ) return [node_robot_state_publisher] + def generate_launch_description(): declare_pkg_name = DeclareLaunchArgument( - 'package_name', - default_value='upao_robot_description', - description='Name of the package containing the robot description' + "package_name", + default_value="upao_robot_description", + description="Name of the package containing the robot description", ) declare_urdf_file = DeclareLaunchArgument( - 'urdf_file', - default_value='robot_wrapper.urdf.xacro', - description='URDF file to load for the robot description' + "urdf_file", + default_value="robot_wrapper.urdf.xacro", + description="URDF file to load for the robot description", ) declare_use_sim_time = DeclareLaunchArgument( - 'use_sim_time', - default_value='true', - description='Use simulation time if true' + "use_sim_time", default_value="true", description="Use simulation time if true" ) - return LaunchDescription([ - declare_pkg_name, - declare_urdf_file, - declare_use_sim_time, - OpaqueFunction(function=launch_setup), - ]) \ No newline at end of file + return LaunchDescription( + [ + declare_pkg_name, + declare_urdf_file, + declare_use_sim_time, + OpaqueFunction(function=launch_setup), + ] + ) diff --git a/parser/tests/real_cases/launch_files/launch_file_5.py b/parser/tests/real_cases/launch_files/launch_file_5.py index 830acb3..dcad0db 100644 --- a/parser/tests/real_cases/launch_files/launch_file_5.py +++ b/parser/tests/real_cases/launch_files/launch_file_5.py @@ -12,55 +12,66 @@ # See the License for the specific language governing permissions and # limitations under the License. +import os + from launch import LaunchDescription -from launch.actions import DeclareLaunchArgument, GroupAction, IncludeLaunchDescription, OpaqueFunction +from launch.actions import ( + DeclareLaunchArgument, + GroupAction, + IncludeLaunchDescription, + OpaqueFunction, +) from launch.launch_description_sources import PythonLaunchDescriptionSource from launch.substitutions import LaunchConfiguration from launch_ros.actions import Node -import os + def launch_setup(context, *args, **kwargs): use_sim_time = LaunchConfiguration("use_sim_time").perform(context) sim = use_sim_time.lower() in ["true", "1"] - + nodes = [] - nodes.append(Node( - package="demo_camera", - executable="camera_node", - parameters=[{"use_sim_time": sim}], - name="camera" - )) + nodes.append( + Node( + package="demo_camera", + executable="camera_node", + parameters=[{"use_sim_time": sim}], + name="camera", + ) + ) - nodes.append(Node( - package="demo_robot", - executable="robot_state_publisher", - parameters=[{"use_sim_time": sim}], - name="state_pub" - )) + nodes.append( + Node( + package="demo_robot", + executable="robot_state_publisher", + parameters=[{"use_sim_time": sim}], + name="state_pub", + ) + ) return nodes + def generate_launch_description(): - return LaunchDescription([ - DeclareLaunchArgument("use_sim_time", default_value="true"), - DeclareLaunchArgument("sub_package"), - GroupAction([ - Node( - package="demo_bringup", - executable="base_node", - name="base" + return LaunchDescription( + [ + DeclareLaunchArgument("use_sim_time", default_value="true"), + DeclareLaunchArgument("sub_package"), + GroupAction( + [ + Node(package="demo_bringup", executable="base_node", name="base"), + IncludeLaunchDescription( + PythonLaunchDescriptionSource( + os.path.join( + LaunchConfiguration("sub_package").perform({}), + "launch", + "sub_launch.py", + ) + ) + ), + ] ), - IncludeLaunchDescription( - PythonLaunchDescriptionSource( - os.path.join( - LaunchConfiguration("sub_package").perform({}), - "launch", - "sub_launch.py" - ) - ) - ) - ]), - - OpaqueFunction(function=launch_setup) - ]) + OpaqueFunction(function=launch_setup), + ] + ) diff --git a/parser/tests/real_cases/launch_files/launch_file_6.py b/parser/tests/real_cases/launch_files/launch_file_6.py index 128c301..5c70e29 100644 --- a/parser/tests/real_cases/launch_files/launch_file_6.py +++ b/parser/tests/real_cases/launch_files/launch_file_6.py @@ -12,26 +12,31 @@ # See the License for the specific language governing permissions and # limitations under the License. + from launch import LaunchDescription -from launch.actions import DeclareLaunchArgument, GroupAction, IncludeLaunchDescription, OpaqueFunction +from launch.actions import ( + DeclareLaunchArgument, + GroupAction, + IncludeLaunchDescription, + OpaqueFunction, +) from launch.conditions import IfCondition +from launch.launch_description_sources import PythonLaunchDescriptionSource from launch.substitutions import LaunchConfiguration, PythonExpression -from launch_ros.actions import Node, ComposableNodeContainer +from launch_ros.actions import ComposableNodeContainer, Node from launch_ros.descriptions import ComposableNode -from launch.launch_description_sources import PythonLaunchDescriptionSource -import os def launch_setup(context, *args, **kwargs): - use_sim_time = LaunchConfiguration('use_sim_time').perform(context) - robot_description = f"..." + use_sim_time = LaunchConfiguration("use_sim_time").perform(context) + robot_description = "..." return [ Node( - package='robot_state_publisher', - executable='robot_state_publisher', - output='screen', - parameters=[{'use_sim_time': use_sim_time, 'robot_description': robot_description}] + package="robot_state_publisher", + executable="robot_state_publisher", + output="screen", + parameters=[{"use_sim_time": use_sim_time, "robot_description": robot_description}], ) ] @@ -39,37 +44,34 @@ def launch_setup(context, *args, **kwargs): def generate_launch_description(): # Declare arguments package_arg = DeclareLaunchArgument( - 'pkg_name', - default_value='my_robot_pkg', - description='Package containing the launch files' + "pkg_name", default_value="my_robot_pkg", description="Package containing the launch files" ) use_sim_arg = DeclareLaunchArgument( - 'use_sim_time', - default_value='true', - description='Use simulation clock' + "use_sim_time", default_value="true", description="Use simulation clock" ) # Include included_launch = IncludeLaunchDescription( - PythonLaunchDescriptionSource([ - LaunchConfiguration('pkg_name'), - '/launch/other_launch_file.py' - ]) + PythonLaunchDescriptionSource( + [LaunchConfiguration("pkg_name"), "/launch/other_launch_file.py"] + ) ) # Group with condition conditional_group = GroupAction( actions=[ Node( - package='demo_nodes_cpp', - executable='talker', - name='conditional_talker', - output='screen', - parameters=[{'use_sim_time': LaunchConfiguration('use_sim_time')}] + package="demo_nodes_cpp", + executable="talker", + name="conditional_talker", + output="screen", + parameters=[{"use_sim_time": LaunchConfiguration("use_sim_time")}], ) ], - condition=IfCondition(PythonExpression(["'", LaunchConfiguration('use_sim_time'), "' == 'true'"])), - namespace='sim_ns' + condition=IfCondition( + PythonExpression(["'", LaunchConfiguration("use_sim_time"), "' == 'true'"]) + ), + namespace="sim_ns", ) # OpaqueFunction @@ -77,32 +79,27 @@ def generate_launch_description(): # Composable Container + Nodes container = ComposableNodeContainer( - name='my_container', - namespace='', - package='rclcpp_components', - executable='component_container_mt', + name="my_container", + namespace="", + package="rclcpp_components", + executable="component_container_mt", composable_node_descriptions=[ ComposableNode( - package='demo_nodes_cpp', - plugin='demo_nodes_cpp::Talker', - name='talker_component', - parameters=[{'use_sim_time': LaunchConfiguration('use_sim_time')}] + package="demo_nodes_cpp", + plugin="demo_nodes_cpp::Talker", + name="talker_component", + parameters=[{"use_sim_time": LaunchConfiguration("use_sim_time")}], ), ComposableNode( - package='demo_nodes_cpp', - plugin='demo_nodes_cpp::Listener', - name='listener_component', - parameters=[{'use_sim_time': LaunchConfiguration('use_sim_time')}] - ) + package="demo_nodes_cpp", + plugin="demo_nodes_cpp::Listener", + name="listener_component", + parameters=[{"use_sim_time": LaunchConfiguration("use_sim_time")}], + ), ], - output='screen' + output="screen", ) - return LaunchDescription([ - package_arg, - use_sim_arg, - included_launch, - conditional_group, - opaque, - container - ]) + return LaunchDescription( + [package_arg, use_sim_arg, included_launch, conditional_group, opaque, container] + ) diff --git a/parser/tests/real_cases/launch_files/nav2_bringup.launch.py b/parser/tests/real_cases/launch_files/nav2_bringup.launch.py index 36e0600..5a0be08 100644 --- a/parser/tests/real_cases/launch_files/nav2_bringup.launch.py +++ b/parser/tests/real_cases/launch_files/nav2_bringup.launch.py @@ -16,8 +16,12 @@ from ament_index_python.packages import get_package_share_directory from launch import LaunchDescription -from launch.actions import (DeclareLaunchArgument, GroupAction, IncludeLaunchDescription, - SetEnvironmentVariable) +from launch.actions import ( + DeclareLaunchArgument, + GroupAction, + IncludeLaunchDescription, + SetEnvironmentVariable, +) from launch.conditions import IfCondition from launch.launch_description_sources import PythonLaunchDescriptionSource from launch.substitutions import LaunchConfiguration, PythonExpression @@ -28,32 +32,32 @@ def generate_launch_description() -> LaunchDescription: # Get the launch directory - bringup_dir = get_package_share_directory('nav2_bringup') - launch_dir = os.path.join(bringup_dir, 'launch') + bringup_dir = get_package_share_directory("nav2_bringup") + launch_dir = os.path.join(bringup_dir, "launch") # Create the launch configuration variables - namespace = LaunchConfiguration('namespace') - slam = LaunchConfigAsBool('slam') - map_yaml_file = LaunchConfiguration('map') - keepout_mask_yaml_file = LaunchConfiguration('keepout_mask') - speed_mask_yaml_file = LaunchConfiguration('speed_mask') - graph_filepath = LaunchConfiguration('graph') - use_sim_time = LaunchConfigAsBool('use_sim_time') - params_file = LaunchConfiguration('params_file') - autostart = LaunchConfigAsBool('autostart') - use_composition = LaunchConfigAsBool('use_composition') - use_respawn = LaunchConfigAsBool('use_respawn') - log_level = LaunchConfiguration('log_level') - use_localization = LaunchConfigAsBool('use_localization') - use_keepout_zones = LaunchConfigAsBool('use_keepout_zones') - use_speed_zones = LaunchConfigAsBool('use_speed_zones') + namespace = LaunchConfiguration("namespace") + slam = LaunchConfigAsBool("slam") + map_yaml_file = LaunchConfiguration("map") + keepout_mask_yaml_file = LaunchConfiguration("keepout_mask") + speed_mask_yaml_file = LaunchConfiguration("speed_mask") + graph_filepath = LaunchConfiguration("graph") + use_sim_time = LaunchConfigAsBool("use_sim_time") + params_file = LaunchConfiguration("params_file") + autostart = LaunchConfigAsBool("autostart") + use_composition = LaunchConfigAsBool("use_composition") + use_respawn = LaunchConfigAsBool("use_respawn") + log_level = LaunchConfiguration("log_level") + use_localization = LaunchConfigAsBool("use_localization") + use_keepout_zones = LaunchConfigAsBool("use_keepout_zones") + use_speed_zones = LaunchConfigAsBool("use_speed_zones") # Map fully qualified names to relative ones so the node's namespace can be prepended. - remappings = [('/tf', 'tf'), ('/tf_static', 'tf_static')] + remappings = [("/tf", "tf"), ("/tf_static", "tf_static")] yaml_substitutions = { - 'KEEPOUT_ZONE_ENABLED': use_keepout_zones, - 'SPEED_ZONE_ENABLED': use_speed_zones, + "KEEPOUT_ZONE_ENABLED": use_keepout_zones, + "SPEED_ZONE_ENABLED": use_speed_zones, } configured_params = ParameterFile( @@ -67,84 +71,80 @@ def generate_launch_description() -> LaunchDescription: allow_substs=True, ) - stdout_linebuf_envvar = SetEnvironmentVariable( - 'RCUTILS_LOGGING_BUFFERED_STREAM', '1' - ) + stdout_linebuf_envvar = SetEnvironmentVariable("RCUTILS_LOGGING_BUFFERED_STREAM", "1") declare_namespace_cmd = DeclareLaunchArgument( - 'namespace', default_value='', description='Top-level namespace' + "namespace", default_value="", description="Top-level namespace" ) declare_slam_cmd = DeclareLaunchArgument( - 'slam', default_value='False', description='Whether run a SLAM' + "slam", default_value="False", description="Whether run a SLAM" ) declare_map_yaml_cmd = DeclareLaunchArgument( - 'map', default_value='', description='Full path to map yaml file to load' + "map", default_value="", description="Full path to map yaml file to load" ) declare_keepout_mask_yaml_cmd = DeclareLaunchArgument( - 'keepout_mask', default_value='', - description='Full path to keepout mask yaml file to load' + "keepout_mask", default_value="", description="Full path to keepout mask yaml file to load" ) declare_speed_mask_yaml_cmd = DeclareLaunchArgument( - 'speed_mask', default_value='', - description='Full path to speed mask yaml file to load' + "speed_mask", default_value="", description="Full path to speed mask yaml file to load" ) declare_graph_file_cmd = DeclareLaunchArgument( - 'graph', - default_value='', description='Path to the graph file to load' + "graph", default_value="", description="Path to the graph file to load" ) declare_use_localization_cmd = DeclareLaunchArgument( - 'use_localization', default_value='True', - description='Whether to enable localization or not' + "use_localization", + default_value="True", + description="Whether to enable localization or not", ) declare_use_keepout_zones_cmd = DeclareLaunchArgument( - 'use_keepout_zones', default_value='True', - description='Whether to enable keepout zones or not' + "use_keepout_zones", + default_value="True", + description="Whether to enable keepout zones or not", ) declare_use_speed_zones_cmd = DeclareLaunchArgument( - 'use_speed_zones', default_value='True', - description='Whether to enable speed zones or not' + "use_speed_zones", default_value="True", description="Whether to enable speed zones or not" ) declare_use_sim_time_cmd = DeclareLaunchArgument( - 'use_sim_time', - default_value='false', - description='Use simulation (Gazebo) clock if true', + "use_sim_time", + default_value="false", + description="Use simulation (Gazebo) clock if true", ) declare_params_file_cmd = DeclareLaunchArgument( - 'params_file', - default_value=os.path.join(bringup_dir, 'params', 'nav2_params.yaml'), - description='Full path to the ROS2 parameters file to use for all launched nodes', + "params_file", + default_value=os.path.join(bringup_dir, "params", "nav2_params.yaml"), + description="Full path to the ROS2 parameters file to use for all launched nodes", ) declare_autostart_cmd = DeclareLaunchArgument( - 'autostart', - default_value='true', - description='Automatically startup the nav2 stack', + "autostart", + default_value="true", + description="Automatically startup the nav2 stack", ) declare_use_composition_cmd = DeclareLaunchArgument( - 'use_composition', - default_value='True', - description='Whether to use composed bringup', + "use_composition", + default_value="True", + description="Whether to use composed bringup", ) declare_use_respawn_cmd = DeclareLaunchArgument( - 'use_respawn', - default_value='False', - description='Whether to respawn if a node crashes. Applied when composition is disabled.', + "use_respawn", + default_value="False", + description="Whether to respawn if a node crashes. Applied when composition is disabled.", ) declare_log_level_cmd = DeclareLaunchArgument( - 'log_level', default_value='info', description='log level' + "log_level", default_value="info", description="log level" ) # Specify the actions @@ -152,92 +152,79 @@ def generate_launch_description() -> LaunchDescription: [ Node( condition=IfCondition(use_composition), - name='nav2_container', + name="nav2_container", namespace=namespace, - package='rclcpp_components', - executable='component_container_isolated', - parameters=[configured_params, {'autostart': autostart}], - arguments=['--ros-args', '--log-level', log_level], + package="rclcpp_components", + executable="component_container_isolated", + parameters=[configured_params, {"autostart": autostart}], + arguments=["--ros-args", "--log-level", log_level], remappings=remappings, - output='screen', + output="screen", ), IncludeLaunchDescription( - PythonLaunchDescriptionSource( - os.path.join(launch_dir, 'slam_launch.py') - ), - condition=IfCondition(PythonExpression([slam, ' and ', use_localization])), + PythonLaunchDescriptionSource(os.path.join(launch_dir, "slam_launch.py")), + condition=IfCondition(PythonExpression([slam, " and ", use_localization])), launch_arguments={ - 'namespace': namespace, - 'use_sim_time': use_sim_time, - 'autostart': autostart, - 'use_respawn': use_respawn, - 'params_file': params_file, + "namespace": namespace, + "use_sim_time": use_sim_time, + "autostart": autostart, + "use_respawn": use_respawn, + "params_file": params_file, }.items(), ), IncludeLaunchDescription( - PythonLaunchDescriptionSource( - os.path.join(launch_dir, 'localization_launch.py') - ), - condition=IfCondition(PythonExpression(['not ', slam, ' and ', use_localization])), + PythonLaunchDescriptionSource(os.path.join(launch_dir, "localization_launch.py")), + condition=IfCondition(PythonExpression(["not ", slam, " and ", use_localization])), launch_arguments={ - 'namespace': namespace, - 'map': map_yaml_file, - 'use_sim_time': use_sim_time, - 'autostart': autostart, - 'params_file': params_file, - 'use_composition': use_composition, - 'use_respawn': use_respawn, - 'container_name': 'nav2_container', + "namespace": namespace, + "map": map_yaml_file, + "use_sim_time": use_sim_time, + "autostart": autostart, + "params_file": params_file, + "use_composition": use_composition, + "use_respawn": use_respawn, + "container_name": "nav2_container", }.items(), ), - IncludeLaunchDescription( - PythonLaunchDescriptionSource( - os.path.join(launch_dir, 'keepout_zone_launch.py') - ), + PythonLaunchDescriptionSource(os.path.join(launch_dir, "keepout_zone_launch.py")), condition=IfCondition(use_keepout_zones), launch_arguments={ - 'namespace': namespace, - 'keepout_mask': keepout_mask_yaml_file, - 'use_sim_time': use_sim_time, - 'params_file': params_file, - 'use_composition': use_composition, - 'use_respawn': use_respawn, - 'container_name': 'nav2_container', + "namespace": namespace, + "keepout_mask": keepout_mask_yaml_file, + "use_sim_time": use_sim_time, + "params_file": params_file, + "use_composition": use_composition, + "use_respawn": use_respawn, + "container_name": "nav2_container", }.items(), ), - IncludeLaunchDescription( - PythonLaunchDescriptionSource( - os.path.join(launch_dir, 'speed_zone_launch.py') - ), + PythonLaunchDescriptionSource(os.path.join(launch_dir, "speed_zone_launch.py")), condition=IfCondition(use_speed_zones), launch_arguments={ - 'namespace': namespace, - 'speed_mask': speed_mask_yaml_file, - 'use_sim_time': use_sim_time, - 'params_file': params_file, - 'use_composition': use_composition, - 'use_respawn': use_respawn, - 'container_name': 'nav2_container', + "namespace": namespace, + "speed_mask": speed_mask_yaml_file, + "use_sim_time": use_sim_time, + "params_file": params_file, + "use_composition": use_composition, + "use_respawn": use_respawn, + "container_name": "nav2_container", }.items(), ), - IncludeLaunchDescription( - PythonLaunchDescriptionSource( - os.path.join(launch_dir, 'navigation_launch.py') - ), + PythonLaunchDescriptionSource(os.path.join(launch_dir, "navigation_launch.py")), launch_arguments={ - 'namespace': namespace, - 'use_sim_time': use_sim_time, - 'autostart': autostart, - 'graph': graph_filepath, - 'params_file': params_file, - 'use_composition': use_composition, - 'use_respawn': use_respawn, - 'use_keepout_zones': use_keepout_zones, - 'use_speed_zones': use_speed_zones, - 'container_name': 'nav2_container', + "namespace": namespace, + "use_sim_time": use_sim_time, + "autostart": autostart, + "graph": graph_filepath, + "params_file": params_file, + "use_composition": use_composition, + "use_respawn": use_respawn, + "use_keepout_zones": use_keepout_zones, + "use_speed_zones": use_speed_zones, + "container_name": "nav2_container", }.items(), ), ] @@ -269,4 +256,4 @@ def generate_launch_description() -> LaunchDescription: # Add the actions to launch all of the navigation nodes ld.add_action(bringup_cmd_group) - return ld \ No newline at end of file + return ld diff --git a/parser/tests/real_cases/launch_files/rrbot.launch.py b/parser/tests/real_cases/launch_files/rrbot.launch.py index 1c7e3ea..ca044c3 100644 --- a/parser/tests/real_cases/launch_files/rrbot.launch.py +++ b/parser/tests/real_cases/launch_files/rrbot.launch.py @@ -18,7 +18,6 @@ from launch.conditions import IfCondition from launch.event_handlers import OnProcessExit from launch.substitutions import Command, FindExecutable, LaunchConfiguration, PathJoinSubstitution - from launch_ros.actions import Node from launch_ros.substitutions import FindPackageShare @@ -122,4 +121,4 @@ def generate_launch_description(): delay_joint_state_broadcaster_after_robot_controller_spawner, ] - return LaunchDescription(declared_arguments + nodes) \ No newline at end of file + return LaunchDescription(declared_arguments + nodes) diff --git a/parser/tests/real_cases/launch_files/turtlebot3_bringup.launch.py b/parser/tests/real_cases/launch_files/turtlebot3_bringup.launch.py index a526630..a164c81 100644 --- a/parser/tests/real_cases/launch_files/turtlebot3_bringup.launch.py +++ b/parser/tests/real_cases/launch_files/turtlebot3_bringup.launch.py @@ -20,100 +20,101 @@ from ament_index_python.packages import get_package_share_directory from launch import LaunchDescription -from launch.actions import DeclareLaunchArgument -from launch.actions import IncludeLaunchDescription +from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription from launch.launch_description_sources import PythonLaunchDescriptionSource -from launch.substitutions import LaunchConfiguration -from launch.substitutions import ThisLaunchFileDir -from launch_ros.actions import Node -from launch_ros.actions import PushRosNamespace +from launch.substitutions import LaunchConfiguration, ThisLaunchFileDir +from launch_ros.actions import Node, PushRosNamespace def generate_launch_description(): - TURTLEBOT3_MODEL = os.environ.get('TURTLEBOT3_MODEL') - ROS_DISTRO = os.environ.get('ROS_DISTRO') - LDS_MODEL = os.environ.get('LDS_MODEL') + TURTLEBOT3_MODEL = os.environ.get("TURTLEBOT3_MODEL") + ROS_DISTRO = os.environ.get("ROS_DISTRO") + LDS_MODEL = os.environ.get("LDS_MODEL") - namespace = LaunchConfiguration('namespace', default='') + namespace = LaunchConfiguration("namespace", default="") - usb_port = LaunchConfiguration('usb_port', default='/dev/ttyACM0') + usb_port = LaunchConfiguration("usb_port", default="/dev/ttyACM0") - if ROS_DISTRO == 'humble': + if ROS_DISTRO == "humble": tb3_param_dir = LaunchConfiguration( - 'tb3_param_dir', + "tb3_param_dir", default=os.path.join( - get_package_share_directory('turtlebot3_bringup'), - 'param', + get_package_share_directory("turtlebot3_bringup"), + "param", ROS_DISTRO, - TURTLEBOT3_MODEL + '.yaml')) + TURTLEBOT3_MODEL + ".yaml", + ), + ) else: tb3_param_dir = LaunchConfiguration( - 'tb3_param_dir', + "tb3_param_dir", default=os.path.join( - get_package_share_directory('turtlebot3_bringup'), - 'param', - TURTLEBOT3_MODEL + '.yaml')) + get_package_share_directory("turtlebot3_bringup"), + "param", + TURTLEBOT3_MODEL + ".yaml", + ), + ) - if LDS_MODEL == 'LDS-01': + if LDS_MODEL == "LDS-01": lidar_pkg_dir = LaunchConfiguration( - 'lidar_pkg_dir', - default=os.path.join(get_package_share_directory('hls_lfcd_lds_driver'), 'launch')) - elif LDS_MODEL == 'LDS-02': + "lidar_pkg_dir", + default=os.path.join(get_package_share_directory("hls_lfcd_lds_driver"), "launch"), + ) + elif LDS_MODEL == "LDS-02": lidar_pkg_dir = LaunchConfiguration( - 'lidar_pkg_dir', - default=os.path.join(get_package_share_directory('ld08_driver'), 'launch')) - LDS_LAUNCH_FILE = '/ld08.launch.py' - elif LDS_MODEL == 'LDS-03': + "lidar_pkg_dir", + default=os.path.join(get_package_share_directory("ld08_driver"), "launch"), + ) + LDS_LAUNCH_FILE = "/ld08.launch.py" + elif LDS_MODEL == "LDS-03": lidar_pkg_dir = LaunchConfiguration( - 'lidar_pkg_dir', - default=os.path.join(get_package_share_directory('coin_d4_driver'), 'launch')) - LDS_LAUNCH_FILE = '/single_lidar_node.launch.py' + "lidar_pkg_dir", + default=os.path.join(get_package_share_directory("coin_d4_driver"), "launch"), + ) + LDS_LAUNCH_FILE = "/single_lidar_node.launch.py" else: lidar_pkg_dir = LaunchConfiguration( - 'lidar_pkg_dir', - default=os.path.join(get_package_share_directory('hls_lfcd_lds_driver'), 'launch')) - LDS_LAUNCH_FILE = '/hlds_laser.launch.py' - - use_sim_time = LaunchConfiguration('use_sim_time', default='false') - - return LaunchDescription([ - DeclareLaunchArgument( - 'use_sim_time', - default_value=use_sim_time, - description='Use simulation (Gazebo) clock if true'), - - DeclareLaunchArgument( - 'usb_port', - default_value=usb_port, - description='Connected USB port with OpenCR'), - - DeclareLaunchArgument( - 'namespace', - default_value=namespace, - description='Namespace for nodes'), - - PushRosNamespace(namespace), - - IncludeLaunchDescription( - PythonLaunchDescriptionSource( - [ThisLaunchFileDir(), '/turtlebot3_state_publisher.launch.py']), - launch_arguments={'use_sim_time': use_sim_time, - 'namespace': namespace}.items(), - ), - - IncludeLaunchDescription( - PythonLaunchDescriptionSource([lidar_pkg_dir, LDS_LAUNCH_FILE]), - launch_arguments={'port': '/dev/ttyUSB0', - 'frame_id': 'base_scan', - 'namespace': namespace}.items(), - ), - - Node( - package='turtlebot3_node', - executable='turtlebot3_ros', - parameters=[ - tb3_param_dir, - {'namespace': namespace}], - arguments=['-i', usb_port], - output='screen'), - ]) \ No newline at end of file + "lidar_pkg_dir", + default=os.path.join(get_package_share_directory("hls_lfcd_lds_driver"), "launch"), + ) + LDS_LAUNCH_FILE = "/hlds_laser.launch.py" + + use_sim_time = LaunchConfiguration("use_sim_time", default="false") + + return LaunchDescription( + [ + DeclareLaunchArgument( + "use_sim_time", + default_value=use_sim_time, + description="Use simulation (Gazebo) clock if true", + ), + DeclareLaunchArgument( + "usb_port", default_value=usb_port, description="Connected USB port with OpenCR" + ), + DeclareLaunchArgument( + "namespace", default_value=namespace, description="Namespace for nodes" + ), + PushRosNamespace(namespace), + IncludeLaunchDescription( + PythonLaunchDescriptionSource( + [ThisLaunchFileDir(), "/turtlebot3_state_publisher.launch.py"] + ), + launch_arguments={"use_sim_time": use_sim_time, "namespace": namespace}.items(), + ), + IncludeLaunchDescription( + PythonLaunchDescriptionSource([lidar_pkg_dir, LDS_LAUNCH_FILE]), + launch_arguments={ + "port": "/dev/ttyUSB0", + "frame_id": "base_scan", + "namespace": namespace, + }.items(), + ), + Node( + package="turtlebot3_node", + executable="turtlebot3_ros", + parameters=[tb3_param_dir, {"namespace": namespace}], + arguments=["-i", usb_port], + output="screen", + ), + ] + ) diff --git a/parser/tests/real_cases/launch_files/turtlebot4_bringup.launch.py b/parser/tests/real_cases/launch_files/turtlebot4_bringup.launch.py index 899d809..2be3a0d 100644 --- a/parser/tests/real_cases/launch_files/turtlebot4_bringup.launch.py +++ b/parser/tests/real_cases/launch_files/turtlebot4_bringup.launch.py @@ -17,100 +17,103 @@ from ament_index_python.packages import get_package_share_directory - from launch import LaunchDescription -from launch.actions import DeclareLaunchArgument, GroupAction, IncludeLaunchDescription, TimerAction # noqa: E501 +from launch.actions import ( # noqa: E501 + DeclareLaunchArgument, + GroupAction, + IncludeLaunchDescription, + TimerAction, +) +from launch.conditions import IfCondition from launch.launch_description_sources import PythonLaunchDescriptionSource from launch.substitutions import EnvironmentVariable, LaunchConfiguration, PathJoinSubstitution -from launch.conditions import IfCondition from launch_ros.actions import PushRosNamespace - from nav2_common.launch import RewrittenYaml def generate_launch_description(): ld = LaunchDescription() - diagnostics_enable = EnvironmentVariable('TURTLEBOT4_DIAGNOSTICS', default_value='1') - namespace = EnvironmentVariable('ROBOT_NAMESPACE', default_value='') + diagnostics_enable = EnvironmentVariable("TURTLEBOT4_DIAGNOSTICS", default_value="1") + namespace = EnvironmentVariable("ROBOT_NAMESPACE", default_value="") - pkg_turtlebot4_bringup = get_package_share_directory('turtlebot4_bringup') - pkg_turtlebot4_diagnostics = get_package_share_directory('turtlebot4_diagnostics') - pkg_turtlebot4_description = get_package_share_directory('turtlebot4_description') + pkg_turtlebot4_bringup = get_package_share_directory("turtlebot4_bringup") + pkg_turtlebot4_diagnostics = get_package_share_directory("turtlebot4_diagnostics") + pkg_turtlebot4_description = get_package_share_directory("turtlebot4_description") param_file_cmd = DeclareLaunchArgument( - 'param_file', - default_value=PathJoinSubstitution( - [pkg_turtlebot4_bringup, 'config', 'turtlebot4.yaml']), - description='Turtlebot4 Robot param file' + "param_file", + default_value=PathJoinSubstitution([pkg_turtlebot4_bringup, "config", "turtlebot4.yaml"]), + description="Turtlebot4 Robot param file", ) - param_file = LaunchConfiguration('param_file') + param_file = LaunchConfiguration("param_file") namespaced_param_file = RewrittenYaml( - source_file=param_file, - root_key=namespace, - param_rewrites={}, - convert_types=True) + source_file=param_file, root_key=namespace, param_rewrites={}, convert_types=True + ) # Launch files turtlebot4_robot_launch_file = PathJoinSubstitution( - [pkg_turtlebot4_bringup, 'launch', 'robot.launch.py']) + [pkg_turtlebot4_bringup, "launch", "robot.launch.py"] + ) joy_teleop_launch_file = PathJoinSubstitution( - [pkg_turtlebot4_bringup, 'launch', 'joy_teleop.launch.py']) + [pkg_turtlebot4_bringup, "launch", "joy_teleop.launch.py"] + ) diagnostics_launch_file = PathJoinSubstitution( - [pkg_turtlebot4_diagnostics, 'launch', 'diagnostics.launch.py']) + [pkg_turtlebot4_diagnostics, "launch", "diagnostics.launch.py"] + ) rplidar_launch_file = PathJoinSubstitution( - [pkg_turtlebot4_bringup, 'launch', 'rplidar.launch.py']) - oakd_launch_file = PathJoinSubstitution( - [pkg_turtlebot4_bringup, 'launch', 'oakd.launch.py']) + [pkg_turtlebot4_bringup, "launch", "rplidar.launch.py"] + ) + oakd_launch_file = PathJoinSubstitution([pkg_turtlebot4_bringup, "launch", "oakd.launch.py"]) description_launch_file = PathJoinSubstitution( - [pkg_turtlebot4_description, 'launch', 'robot_description.launch.py'] + [pkg_turtlebot4_description, "launch", "robot_description.launch.py"] ) actions = [ - PushRosNamespace(namespace), - - IncludeLaunchDescription( - PythonLaunchDescriptionSource([turtlebot4_robot_launch_file]), - launch_arguments=[('model', 'lite'), - ('param_file', namespaced_param_file)]), - - IncludeLaunchDescription( - PythonLaunchDescriptionSource([joy_teleop_launch_file]), - launch_arguments=[('namespace', namespace)]), - - IncludeLaunchDescription( - PythonLaunchDescriptionSource([rplidar_launch_file])), - - # Delay the OAK-D startup for a bit - # This prevents spiking the current on the USB by having the lidar and camera - # start up at the same time as everything else - TimerAction( - period=30.0, - actions=[ - IncludeLaunchDescription( - PythonLaunchDescriptionSource([oakd_launch_file]), - launch_arguments=[ - ('camera', 'oakd_lite'), - ('namespace', namespace), - ]) - ] - ), - - IncludeLaunchDescription( - PythonLaunchDescriptionSource([description_launch_file]), - launch_arguments=[('model', 'lite')]), - ] - - actions.append(IncludeLaunchDescription( + PushRosNamespace(namespace), + IncludeLaunchDescription( + PythonLaunchDescriptionSource([turtlebot4_robot_launch_file]), + launch_arguments=[("model", "lite"), ("param_file", namespaced_param_file)], + ), + IncludeLaunchDescription( + PythonLaunchDescriptionSource([joy_teleop_launch_file]), + launch_arguments=[("namespace", namespace)], + ), + IncludeLaunchDescription(PythonLaunchDescriptionSource([rplidar_launch_file])), + # Delay the OAK-D startup for a bit + # This prevents spiking the current on the USB by having the lidar and camera + # start up at the same time as everything else + TimerAction( + period=30.0, + actions=[ + IncludeLaunchDescription( + PythonLaunchDescriptionSource([oakd_launch_file]), + launch_arguments=[ + ("camera", "oakd_lite"), + ("namespace", namespace), + ], + ) + ], + ), + IncludeLaunchDescription( + PythonLaunchDescriptionSource([description_launch_file]), + launch_arguments=[("model", "lite")], + ), + ] + + actions.append( + IncludeLaunchDescription( PythonLaunchDescriptionSource([diagnostics_launch_file]), - launch_arguments=[('namespace', namespace)], - condition=IfCondition(diagnostics_enable))) + launch_arguments=[("namespace", namespace)], + condition=IfCondition(diagnostics_enable), + ) + ) turtlebot4_lite = GroupAction(actions) ld = LaunchDescription() ld.add_action(param_file_cmd) ld.add_action(turtlebot4_lite) - return ld \ No newline at end of file + return ld diff --git a/parser/tests/test_from_yaml.py b/parser/tests/test_from_yaml.py index 6918783..b50d7da 100644 --- a/parser/tests/test_from_yaml.py +++ b/parser/tests/test_from_yaml.py @@ -13,66 +13,104 @@ # limitations under the License. import pytest -from parser.tests.test_helpers import load_yaml_tests, load_custom_handler_tests, parse_launch_string + +from parser.tests.test_helpers import ( + load_custom_handler_tests, + load_yaml_tests, + parse_launch_string, +) + @pytest.mark.parametrize("code,expected", load_yaml_tests("test_cases/node_tests.yaml")) def test_node_parsing(code, expected): result = parse_launch_string(code) assert result.get("nodes") == expected.get("nodes") + @pytest.mark.parametrize("code,expected", load_yaml_tests("test_cases/launch_config_tests.yaml")) def test_launch_configuration_parsing(code, expected): result = parse_launch_string(code) - for key in ["nodes", "arguments", "includes", "groups", "parameters", - "launch_argument_usages", "undeclared_launch_configurations"]: + for key in [ + "nodes", + "arguments", + "includes", + "groups", + "parameters", + "launch_argument_usages", + "undeclared_launch_configurations", + ]: assert result.get(key, []) == expected.get(key, []) + @pytest.mark.parametrize("code,expected", load_yaml_tests("test_cases/group_action_tests.yaml")) def test_group_action_parsing(code, expected): result = parse_launch_string(code) for key in ["nodes", "arguments", "includes", "groups", "launch_argument_usages"]: assert result.get(key, []) == expected.get(key, []) + @pytest.mark.parametrize("code,expected", load_yaml_tests("test_cases/include_launch_tests.yaml")) def test_include_launch_parsing(code, expected): result = parse_launch_string(code) - for key in ["arguments", "includes", "launch_argument_usages", "undeclared_launch_configurations"]: + for key in [ + "arguments", + "includes", + "launch_argument_usages", + "undeclared_launch_configurations", + ]: assert result.get(key, []) == expected.get(key, []) + @pytest.mark.parametrize("code,expected", load_yaml_tests("test_cases/opaque_function_tests.yaml")) def test_opaque_functions_parsing(code, expected): result = parse_launch_string(code) - for key in ["arguments", "opaque_functions", "launch_argument_usages", "undeclared_launch_configurations"]: + for key in [ + "arguments", + "opaque_functions", + "launch_argument_usages", + "undeclared_launch_configurations", + ]: assert result.get(key, []) == expected.get(key, []) + @pytest.mark.parametrize("code,expected", load_yaml_tests("test_cases/condition_tests.yaml")) def test_conditions_parsing(code, expected): result = parse_launch_string(code) - for key in ["arguments", "nodes", "groups", "launch_argument_usages", "undeclared_launch_configurations"]: + for key in [ + "arguments", + "nodes", + "groups", + "launch_argument_usages", + "undeclared_launch_configurations", + ]: assert result.get(key, []) == expected.get(key, []) + @pytest.mark.parametrize("code,expected", load_yaml_tests("test_cases/composable_node_tests.yaml")) def test_composable_nodes_parsing(code, expected): result = parse_launch_string(code) - for key in ["arguments", "composable_nodes", "unattached_composable_nodes", "launch_argument_usages", "undeclared_launch_configurations"]: + for key in [ + "arguments", + "composable_nodes", + "unattached_composable_nodes", + "launch_argument_usages", + "undeclared_launch_configurations", + ]: assert result.get(key, []) == expected.get(key, []) + @pytest.mark.parametrize("code,expected", load_yaml_tests("test_cases/event_handler_tests.yaml")) def test_event_handlers_parsing(code, expected): result = parse_launch_string(code) for key in ["nodes", "event_handlers"]: assert result.get(key, []) == expected.get(key, []) -@pytest.mark.parametrize("code,expected", load_custom_handler_tests("test_cases/custom_handlers_tests.yaml", "test_handlers")) + +@pytest.mark.parametrize( + "code,expected", + load_custom_handler_tests("test_cases/custom_handlers_tests.yaml", "test_handlers"), +) def test_custom_handlers_parsing(code, expected): result = parse_launch_string(code) for key in ["arguments", "nodes", "launch_argument_usages", "custom_components"]: assert result.get(key, []) == expected.get(key, []) - -# @pytest.mark.parametrize("code,expected,files", load_yaml_tests("test_cases/recursive_include_tests.yaml", with_files=True)) -# def test_recursive_include_parsing(code, expected, files): -# with mock_tempfile_and_fs(code, files): -# result = parse_launch_string(code) - -# for key in ["nodes", "arguments", "includes", "groups", "launch_argument_usages"]: -# assert result.get(key, []) == expected.get(key, []) \ No newline at end of file diff --git a/parser/tests/test_handlers/custom_launch_thing_handler.py b/parser/tests/test_handlers/custom_launch_thing_handler.py index 6fdc0a1..eae4cca 100644 --- a/parser/tests/test_handlers/custom_launch_thing_handler.py +++ b/parser/tests/test_handlers/custom_launch_thing_handler.py @@ -14,12 +14,11 @@ from parser.parser.user_handler import register_user_handler + @register_user_handler("MyCustomLaunchThing") def handle_custom_launch_thing(node, context): return { "type": "CustomHandler", "type_name": "MyCustomLaunchThing", - "metadata": { - "info": "example" - } - } \ No newline at end of file + "metadata": {"info": "example"}, + } diff --git a/parser/tests/test_handlers/my_launch_handler.py b/parser/tests/test_handlers/my_launch_handler.py index f56a9e4..c057cbe 100644 --- a/parser/tests/test_handlers/my_launch_handler.py +++ b/parser/tests/test_handlers/my_launch_handler.py @@ -16,16 +16,15 @@ from parser.parser.user_handler import register_user_handler from parser.resolution.utils import resolve_call_signature + @register_user_handler("MyLaunch") def handle_my_launch(node, context): args, _ = resolve_call_signature(node, context.engine) if not args: raise ValueError("MyLaunch must have a name.") - + name = args[0] - return simplify_launch_configurations({ - "type": "CustomHandler", - "type_name": "MyLaunch", - "name": name - }) \ No newline at end of file + return simplify_launch_configurations( + {"type": "CustomHandler", "type_name": "MyLaunch", "name": name} + ) diff --git a/parser/tests/test_helpers.py b/parser/tests/test_helpers.py index fe7699a..3ebbd8d 100644 --- a/parser/tests/test_helpers.py +++ b/parser/tests/test_helpers.py @@ -13,16 +13,18 @@ # limitations under the License. import tempfile -import yaml + import pytest -from parser.entrypoint.parser_runner import parse_launch_file +import yaml + from parser.entrypoint.user_interface import parse_and_format_launch_file from parser.plugin_loader import load_user_handlers_from_directory + def load_yaml_tests(file_path): - with open(file_path, 'r') as f: + with open(file_path, "r") as f: data = yaml.safe_load(f) - + tests = [] for test in data["tests"]: code = test["input"] @@ -32,12 +34,14 @@ def load_yaml_tests(file_path): return tests + def load_custom_handler_tests(file_path, handler_directory): load_user_handlers_from_directory(handler_directory) return load_yaml_tests(file_path) + def parse_launch_string(code: str) -> dict: with tempfile.NamedTemporaryFile("w", suffix=".py", delete=False) as tmp: tmp.write(code) tmp.flush() - return parse_and_format_launch_file(tmp.name) \ No newline at end of file + return parse_and_format_launch_file(tmp.name) diff --git a/parser/tests/test_real_launch_files.py b/parser/tests/test_real_launch_files.py index 0c527e7..6a9a841 100644 --- a/parser/tests/test_real_launch_files.py +++ b/parser/tests/test_real_launch_files.py @@ -12,20 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os import json +import os + import pytest -from parser.plugin_loader import load_user_handlers_from_directory + from parser.entrypoint.user_interface import parse_and_format_launch_file +from parser.plugin_loader import load_user_handlers_from_directory BASE_DIR = os.path.dirname(__file__) INPUT_DIR = os.path.join(BASE_DIR, "real_cases/launch_files") OUTPUT_DIR = os.path.join(BASE_DIR, "real_cases/expected_outputs") PLUGIN_DIR = os.path.join(BASE_DIR, "real_cases/launch_files/custom_handlers") -@pytest.mark.parametrize("filename", [ - f for f in os.listdir(INPUT_DIR) if f.endswith(".py") -]) + +@pytest.mark.parametrize("filename", [f for f in os.listdir(INPUT_DIR) if f.endswith(".py")]) def test_real_launch_file_snapshot(filename): input_path = os.path.join(INPUT_DIR, filename) output_path = os.path.join(OUTPUT_DIR, f"{filename}.json") @@ -37,4 +38,3 @@ def test_real_launch_file_snapshot(filename): expected = json.load(open(output_path)) assert result == expected, f"Mismatch in output for {filename}" - diff --git a/parser/tests/test_utils.py b/parser/tests/test_utils.py index e0f6b8d..d569b6f 100644 --- a/parser/tests/test_utils.py +++ b/parser/tests/test_utils.py @@ -13,10 +13,9 @@ # limitations under the License. import io -import os -import tempfile -from unittest.mock import patch, mock_open, MagicMock from contextlib import contextmanager +from unittest.mock import patch + @contextmanager def mock_tempfile_and_fs(code: str, files: dict): @@ -38,7 +37,9 @@ def fake_file_loader(path, *args, **kwargs): return io.StringIO(content) raise FileNotFoundError(f"No such file: {path}") - with patch("tempfile.NamedTemporaryFile", side_effect=fake_named_tempfile), \ - patch("builtins.open", side_effect=fake_file_loader), \ - patch("os.path.exists", side_effect=lambda p: any(p.endswith(name) for name in files)): - yield \ No newline at end of file + with ( + patch("tempfile.NamedTemporaryFile", side_effect=fake_named_tempfile), + patch("builtins.open", side_effect=fake_file_loader), + patch("os.path.exists", side_effect=lambda p: any(p.endswith(name) for name in files)), + ): + yield diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..e164184 --- /dev/null +++ b/setup.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +set -e + +echo "๐Ÿš€ Starting environment setup..." + +# Install Python +if ! command -v python3 &>/dev/null; then + echo "โš ๏ธ Python3 not found. Please install Python 3.8+ manually." + exit 1 +fi +echo "โœ… Python version: $(python3 --version)" + +# Install Node.js + npm +if ! command -v npm &>/dev/null; then + if [[ "$OSTYPE" == "linux-gnu"* ]]; then + echo "โš ๏ธ npm not found. Installing Node.js..." + curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - + sudo apt-get install -y nodejs + elif [[ "$OSTYPE" == "darwin"* ]]; then + brew install node + else + echo "โŒ Unsupported OS for automatic Node.js install" + exit 1 + fi +fi +echo "โœ… Node.js version: $(node -v)" +echo "โœ… npm version: $(npm -v)" + +# Create virtual environment +if [ ! -d ".venv" ]; then + python3 -m venv .venv + echo "โœ… Created virtual environment in .venv" +fi + +# Activate virtual environment +source .venv/bin/activate +echo "โœ… Activated virtual environment" + +# Install Python Dependencies +if [ -f "requirements.txt" ]; then + pip install --upgrade pip + pip install -r requirements.txt + echo "โœ… Installed Python dependencies" +else + echo "โš ๏ธ No requirements.txt found, skipping Python deps install" +fi + +# Install npm Dependencies +if [ -f "package.json" ]; then + npm install + echo "โœ… Installed npm dependencies" +else + echo "โš ๏ธ No package.json found, skipping npm deps install" +fi + +# Install Playwright & browser dependencies +echo "๐Ÿ“ฆ Installing Playwright..." +npm install -D playwright +npx playwright install --with-deps +echo "โœ… Playwright installed with dependencies" + +echo "๐ŸŽ‰ Setup complete!" \ No newline at end of file diff --git a/src/commands/exportJson.ts b/src/commands/exportJson.ts index d0dd0b0..b2a8bf1 100644 --- a/src/commands/exportJson.ts +++ b/src/commands/exportJson.ts @@ -17,47 +17,47 @@ import { runPythonParser } from '../python/runParser'; import { getLastParsedData, setLastParsedData } from './openVisualizer'; export function registerExportJson(context: vscode.ExtensionContext) { - context.subscriptions.push( - vscode.commands.registerCommand('launchmap.exportAsJson', async (): Promise => { - let graphDataToExport = null; + context.subscriptions.push( + vscode.commands.registerCommand('launchmap.exportAsJson', async (): Promise => { + let graphDataToExport = null; - const editor = vscode.window.activeTextEditor; + const editor = vscode.window.activeTextEditor; - if (editor) { - const filePath = editor.document.fileName; - try { - const result = await runPythonParser(filePath); - graphDataToExport = JSON.parse(result); - setLastParsedData(graphDataToExport); - } catch (error) { - vscode.window.showErrorMessage("Failed to parse the launch file."); - return null; - } - } else { - graphDataToExport = getLastParsedData(); - if (!graphDataToExport) { - vscode.window.showErrorMessage("No active editor and no graph data available to export."); - return null; - } - } + if (editor) { + const filePath = editor.document.fileName; + try { + const result = await runPythonParser(filePath); + graphDataToExport = JSON.parse(result); + setLastParsedData(graphDataToExport); + } catch { + vscode.window.showErrorMessage('Failed to parse the launch file.'); + return null; + } + } else { + graphDataToExport = getLastParsedData(); + if (!graphDataToExport) { + vscode.window.showErrorMessage('No active editor and no graph data available to export.'); + return null; + } + } - const uri = await vscode.window.showSaveDialog({ - filters: { 'JSON': ['json'] }, - defaultUri: vscode.Uri.file('launch_graph.json'), - saveLabel: 'Export Launch Graph' - }); + const uri = await vscode.window.showSaveDialog({ + filters: { 'JSON': ['json'] }, + defaultUri: vscode.Uri.file('launch_graph.json'), + saveLabel: 'Export Launch Graph' + }); - if (!uri) return null; + if (!uri) return null; - const jsonString = JSON.stringify(graphDataToExport, null, 2); - try { - await vscode.workspace.fs.writeFile(uri, Buffer.from(jsonString, 'utf8')); - vscode.window.showInformationMessage(`Launch graph exported to ${uri.fsPath}`); - return uri.fsPath; - } catch (error) { - vscode.window.showErrorMessage("Failed to save JSON: " + (error as Error).message); - return null; - } - }) - ); -} \ No newline at end of file + const jsonString = JSON.stringify(graphDataToExport, null, 2); + try { + await vscode.workspace.fs.writeFile(uri, Buffer.from(jsonString, 'utf8')); + vscode.window.showInformationMessage(`Launch graph exported to ${uri.fsPath}`); + return uri.fsPath; + } catch (error) { + vscode.window.showErrorMessage('Failed to save JSON: ' + (error as Error).message); + return null; + } + }) + ); +} diff --git a/src/commands/importJson.ts b/src/commands/importJson.ts index 2bbd547..bb4072c 100644 --- a/src/commands/importJson.ts +++ b/src/commands/importJson.ts @@ -18,28 +18,28 @@ import { createVisualizerPanel } from '../panel/createVisualizerPanel'; import { setLastParsedData } from './openVisualizer'; export function registerImportJson(context: vscode.ExtensionContext) { - context.subscriptions.push( - vscode.commands.registerCommand('launchmap.importJson', async () => { - const fileUris = await vscode.window.showOpenDialog({ - canSelectMany: false, - filters: { 'JSON': ['json'] }, - openLabel: 'Import Launch Graph JSON' - }); + context.subscriptions.push( + vscode.commands.registerCommand('launchmap.importJson', async () => { + const fileUris = await vscode.window.showOpenDialog({ + canSelectMany: false, + filters: { 'JSON': ['json'] }, + openLabel: 'Import Launch Graph JSON' + }); - if (!fileUris || fileUris.length === 0) return; + if (!fileUris || fileUris.length === 0) return; - try { - const importedName = path.basename(fileUris[0].fsPath); + try { + const importedName = path.basename(fileUris[0].fsPath); - const contentBytes = await vscode.workspace.fs.readFile(fileUris[0]); - const content = Buffer.from(contentBytes).toString('utf8'); - const parsed = JSON.parse(content); + const contentBytes = await vscode.workspace.fs.readFile(fileUris[0]); + const content = Buffer.from(contentBytes).toString('utf8'); + const parsed = JSON.parse(content); - setLastParsedData(parsed); - createVisualizerPanel(context, parsed, importedName); - } catch (error) { - vscode.window.showErrorMessage("Failed to load or parse the JSON file."); - } - }) - ); -} \ No newline at end of file + setLastParsedData(parsed); + createVisualizerPanel(context, parsed, importedName); + } catch { + vscode.window.showErrorMessage('Failed to load or parse the JSON file.'); + } + }) + ); +} diff --git a/src/commands/openVisualizer.ts b/src/commands/openVisualizer.ts index 0ebe900..25081f3 100644 --- a/src/commands/openVisualizer.ts +++ b/src/commands/openVisualizer.ts @@ -17,31 +17,31 @@ import * as path from 'path'; import { runPythonParser } from '../python/runParser'; import { createVisualizerPanel } from '../panel/createVisualizerPanel'; -let lastParsedData: any = null; +let lastParsedData: null = null; export function registerOpenVisualizer(context: vscode.ExtensionContext) { - context.subscriptions.push( - vscode.commands.registerCommand('launchmap.openVisualizer', async() => { - const editor = vscode.window.activeTextEditor; - if (!editor) { - vscode.window.showErrorMessage("No active editor with a launch file."); - return; - } + context.subscriptions.push( + vscode.commands.registerCommand('launchmap.openVisualizer', async() => { + const editor = vscode.window.activeTextEditor; + if (!editor) { + vscode.window.showErrorMessage('No active editor with a launch file.'); + return; + } - const filePath = editor.document.fileName; - const fileName = path.basename(filePath); - const result = await runPythonParser(filePath); + const filePath = editor.document.fileName; + const fileName = path.basename(filePath); + const result = await runPythonParser(filePath); - lastParsedData = JSON.parse(result); - createVisualizerPanel(context, lastParsedData, fileName); - }) - ); + lastParsedData = JSON.parse(result); + createVisualizerPanel(context, lastParsedData, fileName); + }) + ); } export function getLastParsedData() { - return lastParsedData; + return lastParsedData; } -export function setLastParsedData(data: any) { - lastParsedData = data; -} \ No newline at end of file +export function setLastParsedData(data: null) { + lastParsedData = data; +} diff --git a/src/commands/setPluginDir.ts b/src/commands/setPluginDir.ts index 42406de..5091e02 100644 --- a/src/commands/setPluginDir.ts +++ b/src/commands/setPluginDir.ts @@ -17,20 +17,20 @@ import { setPluginDir } from '../utils/launchmapConfig'; import { updatePluginDirStatusBar } from '../ui/pluginDirStatusBar'; export function registerSetPluginDir(context: vscode.ExtensionContext) { - context.subscriptions.push( - vscode.commands.registerCommand('launchmap.setPluginDir', async () => { - const selected = await vscode.window.showOpenDialog({ - canSelectFiles: false, - canSelectFolders: true, - canSelectMany: false, - openLabel: 'Select Plugin Directory' - }); + context.subscriptions.push( + vscode.commands.registerCommand('launchmap.setPluginDir', async () => { + const selected = await vscode.window.showOpenDialog({ + canSelectFiles: false, + canSelectFolders: true, + canSelectMany: false, + openLabel: 'Select Plugin Directory' + }); - if (!selected || selected.length === 0) return; + if (!selected || selected.length === 0) return; - const pluginPath = selected[0].fsPath; - await setPluginDir(pluginPath); - await updatePluginDirStatusBar(); - }) - ); -} \ No newline at end of file + const pluginPath = selected[0].fsPath; + await setPluginDir(pluginPath); + await updatePluginDirStatusBar(); + }) + ); +} diff --git a/src/extension.ts b/src/extension.ts index 1e740e2..c016741 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -20,10 +20,10 @@ import { registerSetPluginDir } from './commands/setPluginDir'; import { initPluginDirStatusBar } from './ui/pluginDirStatusBar'; export function activate(context: vscode.ExtensionContext) { - registerOpenVisualizer(context); - registerExportJson(context); - registerImportJson(context); - registerSetPluginDir(context); + registerOpenVisualizer(context); + registerExportJson(context); + registerImportJson(context); + registerSetPluginDir(context); - initPluginDirStatusBar(); -} \ No newline at end of file + initPluginDirStatusBar(); +} diff --git a/src/panel/createVisualizerPanel.ts b/src/panel/createVisualizerPanel.ts index c25b7d2..6046b58 100644 --- a/src/panel/createVisualizerPanel.ts +++ b/src/panel/createVisualizerPanel.ts @@ -16,41 +16,41 @@ import * as vscode from 'vscode'; import { getWebviewHtml } from './getWebviewHtml'; export function createVisualizerPanel( - context: vscode.ExtensionContext, - data: any, - titleHint?: string + context: vscode.ExtensionContext, + data: null, + titleHint?: string ): vscode.WebviewPanel { - const panelTitle = titleHint - ? `${titleHint} : ROS2 LaunchMap Visualizer` - : 'ROS2 LaunchMap Visualizer' + const panelTitle = titleHint + ? `${titleHint} : ROS2 LaunchMap Visualizer` + : 'ROS2 LaunchMap Visualizer'; - const panel = vscode.window.createWebviewPanel( - 'launchmap', - panelTitle, - vscode.ViewColumn.One, - { - enableScripts: true, - retainContextWhenHidden: true - } - ); + const panel = vscode.window.createWebviewPanel( + 'launchmap', + panelTitle, + vscode.ViewColumn.One, + { + enableScripts: true, + retainContextWhenHidden: true + } + ); - panel.webview.html = getWebviewHtml(panel.webview, context.extensionUri); - panel.webview.postMessage({ type: 'launchmap-data', data }); + panel.webview.html = getWebviewHtml(panel.webview, context.extensionUri); + panel.webview.postMessage({ type: 'launchmap-data', data }); - panel.webview.onDidReceiveMessage(async (message) => { - if (message.type === 'export-json') { - const result = await vscode.commands.executeCommand('launchmap.exportAsJson'); - if (result) { - panel.webview.postMessage({ type: 'export-complete', path: result }); - } - } - }); + panel.webview.onDidReceiveMessage(async (message) => { + if (message.type === 'export-json') { + const result = await vscode.commands.executeCommand('launchmap.exportAsJson'); + if (result) { + panel.webview.postMessage({ type: 'export-complete', path: result }); + } + } + }); - panel.onDidChangeViewState((e) => { - if (e.webviewPanel.visible) { - panel.webview.postMessage({ type: 'launchmap-data', data }); - } - }); + panel.onDidChangeViewState((e) => { + if (e.webviewPanel.visible) { + panel.webview.postMessage({ type: 'launchmap-data', data }); + } + }); - return panel; -} \ No newline at end of file + return panel; +} diff --git a/src/panel/getWebviewHtml.ts b/src/panel/getWebviewHtml.ts index 48ed8fb..031a69a 100644 --- a/src/panel/getWebviewHtml.ts +++ b/src/panel/getWebviewHtml.ts @@ -15,10 +15,10 @@ import * as vscode from 'vscode'; export function getWebviewHtml(webview: vscode.Webview, extensionUri: vscode.Uri): string { - const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, 'webview', 'script.js')); - const styleUri = webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, 'webview', 'style.css')); + const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, 'webview', 'script.js')); + const styleUri = webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, 'webview', 'style.css')); - return ` + return ` @@ -33,5 +33,5 @@ export function getWebviewHtml(webview: vscode.Webview, extensionUri: vscode.Uri - ` -} \ No newline at end of file + `; +} diff --git a/src/python/runParser.ts b/src/python/runParser.ts index f12fff8..53183b2 100644 --- a/src/python/runParser.ts +++ b/src/python/runParser.ts @@ -19,44 +19,45 @@ import * as which from 'which'; import { getPluginDir } from '../utils/launchmapConfig'; export async function runPythonParser(filePath: string): Promise { - return new Promise(async (resolve, reject) => { - const pythonCmd = detectPythonCommand(); + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve, reject) => { + const pythonCmd = detectPythonCommand(); - if (!pythonCmd) { - vscode.window.showErrorMessage( - "Python iterpreter not found. Please install Python 3 and make sure it is available in your PATH." - ); - return reject(new Error("No Python interpreter found.")); - } + if (!pythonCmd) { + vscode.window.showErrorMessage( + 'Python iterpreter not found. Please install Python 3 and make sure it is available in your PATH.' + ); + return reject(new Error('No Python interpreter found.')); + } - const scriptPath = path.join(__dirname, '..', '..', 'parse.py'); - const cmdParts = [`"${pythonCmd}"`, `"${scriptPath}"`, `"${filePath}"`]; + const scriptPath = path.join(__dirname, '..', '..', 'parse.py'); + const cmdParts = [`"${pythonCmd}"`, `"${scriptPath}"`, `"${filePath}"`]; - const pluginDir = await getPluginDir(); - if (pluginDir) { - cmdParts.push('--plugin-dir', `"${pluginDir}"`); - } + const pluginDir = await getPluginDir(); + if (pluginDir) { + cmdParts.push('--plugin-dir', `"${pluginDir}"`); + } - const cmd = cmdParts.join(' '); - cp.exec(cmd, (err, stdout, stderr) => { - if (err) { - vscode.window.showErrorMessage("Parser error: " + stderr); - return reject(err); - } - resolve(stdout); - }); + const cmd = cmdParts.join(' '); + cp.exec(cmd, (err, stdout, stderr) => { + if (err) { + vscode.window.showErrorMessage('Parser error: ' + stderr); + return reject(err); + } + resolve(stdout); }); + }); } function detectPythonCommand(): string | null { - const candidates = ['python3', 'python', 'py']; - for (const cmd of candidates) { - try { - which.sync(cmd); - return cmd; - } catch (_) { - continue; - } + const candidates = ['python3', 'python', 'py']; + for (const cmd of candidates) { + try { + which.sync(cmd); + return cmd; + } catch { + continue; } - return null; -} \ No newline at end of file + } + return null; +} diff --git a/src/test/e2e/index.ts b/src/test/e2e/index.ts index 6c4b49c..52e1149 100644 --- a/src/test/e2e/index.ts +++ b/src/test/e2e/index.ts @@ -16,22 +16,22 @@ import * as path from 'path'; import Mocha from 'mocha'; export async function run(): Promise { - const mocha = new Mocha({ ui: 'tdd', timeout: 10000 }); - - mocha.addFile(path.resolve(__dirname, 'workflow.test')); + const mocha = new Mocha({ ui: 'tdd', timeout: 10000 }); - await new Promise((resolve, reject) => { - mocha.run(failures => { - if (failures > 0) { - console.error(`${failures} tests failed.`); - reject(new Error("Test failures")); - } else { - console.log('โœ… All tests passed.'); - resolve(); - } - }); + mocha.addFile(path.resolve(__dirname, 'workflow.test')); + + await new Promise((resolve, reject) => { + mocha.run(failures => { + if (failures > 0) { + console.error(`${failures} tests failed.`); + reject(new Error('Test failures')); + } else { + console.log('โœ… All tests passed.'); + resolve(); + } }); + }); - console.log("All tests done, exiting..."); - process.exit(0); -} \ No newline at end of file + console.log('All tests done, exiting...'); + process.exit(0); +} diff --git a/src/test/e2e/workflow.test.ts b/src/test/e2e/workflow.test.ts index 6774ec4..7d9f1f0 100644 --- a/src/test/e2e/workflow.test.ts +++ b/src/test/e2e/workflow.test.ts @@ -23,87 +23,89 @@ const mkdtemp = promisify(fs.mkdtemp); const rm = promisify(fs.rm || fs.rmdir); suite('End-to-End Workflow Test', () => { - let tempDir: string; - let exportPath: string; - const fixturePath = path.resolve(__dirname, '../../../src/test/fixtures/test.launch.py'); - - suiteSetup(async () => { - // Create a temp directory for export - tempDir = await mkdtemp(path.join(os.tmpdir(), 'launchmap-test-')); - exportPath = path.join(tempDir, 'launch_graph.json'); + let tempDir: string; + let exportPath: string; + const fixturePath = path.resolve(__dirname, '../../../src/test/fixtures/test.launch.py'); + + suiteSetup(async () => { + // Create a temp directory for export + tempDir = await mkdtemp(path.join(os.tmpdir(), 'launchmap-test-')); + exportPath = path.join(tempDir, 'launch_graph.json'); + }); + + test('1๏ธโƒฃ Open Visualizer โ†’ Panel created', async () => { + const doc = await vscode.workspace.openTextDocument(fixturePath); + await vscode.window.showTextDocument(doc); + + await vscode.commands.executeCommand('launchmap.openVisualizer'); + assert.ok(true, 'Visualizer command executed without error'); + }); + + test('2๏ธโƒฃ Export as JSON โ†’ File saved', async () => { + // Mock save dialog to return temp file path + vscode.window.showSaveDialog = async () => vscode.Uri.file(exportPath); + + await vscode.commands.executeCommand('launchmap.exportAsJson'); + const exists = fs.existsSync(exportPath); + assert.ok(exists, 'Expected exported JSON file to exist'); + }); + + test('3๏ธโƒฃ Import JSON โ†’ Panel created again', async () => { + // Mock open dialog to return exported file path + vscode.window.showOpenDialog = async () => [vscode.Uri.file(exportPath)]; + + await vscode.commands.executeCommand('launchmap.importJson'); + assert.ok(true, 'Import command executed without error'); + }); + + test('4๏ธโƒฃ Set Plugin Dir via command โ†’ Stored in .launchmap file', async () => { + const pluginDir = path.join(tempDir, 'plugins'); + await fs.promises.mkdir(pluginDir, { recursive: true }); + const configPath = path.join(tempDir, '.launchmap'); + + // Stub workspace folder to point to temp directory + Object.defineProperty(vscode.workspace, 'workspaceFolders', { + get: () => [{ uri: vscode.Uri.file(tempDir) }], + configurable: true }); - test('1๏ธโƒฃ Open Visualizer โ†’ Panel created', async () => { - const doc = await vscode.workspace.openTextDocument(fixturePath); - await vscode.window.showTextDocument(doc); + vscode.window.showOpenDialog = async () => [vscode.Uri.file(pluginDir)]; - await vscode.commands.executeCommand('launchmap.openVisualizer'); - assert.ok(true, 'Visualizer command executed without error'); - }); - - test('2๏ธโƒฃ Export as JSON โ†’ File saved', async () => { - // Mock save dialog to return temp file path - vscode.window.showSaveDialog = async () => vscode.Uri.file(exportPath); - - await vscode.commands.executeCommand('launchmap.exportAsJson'); - const exists = fs.existsSync(exportPath); - assert.ok(exists, 'Expected exported JSON file to exist'); - }); - - test('3๏ธโƒฃ Import JSON โ†’ Panel created again', async () => { - // Mock open dialog to return exported file path - vscode.window.showOpenDialog = async () => [vscode.Uri.file(exportPath)]; - - await vscode.commands.executeCommand('launchmap.importJson'); - assert.ok(true, 'Import command executed without error'); - }); + await vscode.commands.executeCommand('launchmap.setPluginDir'); - test('4๏ธโƒฃ Set Plugin Dir via command โ†’ Stored in .launchmap file', async () => { - const workspaceDir = path.join(tempDir, 'workspace'); - await fs.promises.mkdir(workspaceDir, { recursive: true }); + const configContent = await fs.promises.readFile(configPath, 'utf8'); + const parsed = JSON.parse(configContent); - vscode.workspace.updateWorkspaceFolders(0, 0, { uri: vscode.Uri.file(workspaceDir) }); + assert.strictEqual(parsed.pluginDir, pluginDir, 'Expected pluginDir to be set correctly in .launchmap'); + }); - const pluginDir = path.join(workspaceDir, 'plugins'); - await fs.promises.mkdir(pluginDir, { recursive: true }); - const configPath = path.join(workspaceDir, '.launchmap'); + test('5๏ธโƒฃ Plugin Dir via .launchmap โ†’ Used in parser call', async () => { + const pluginDir = path.join(tempDir, 'plugins'); + await fs.promises.mkdir(pluginDir, { recursive: true }); + await fs.promises.writeFile(path.join(pluginDir, 'dummpy.py'), '# dummy plugin'); - vscode.window.showOpenDialog = async () => [vscode.Uri.file(pluginDir)]; + const configPath = path.join(tempDir, '.launchmap'); + const config = { pluginDir }; + await fs.promises.writeFile(configPath, JSON.stringify(config, null, 2)); - await vscode.commands.executeCommand('launchmap.setPluginDir'); - - const configContent = await fs.promises.readFile(configPath, 'utf8'); - const parsed = JSON.parse(configContent); - - assert.strictEqual(parsed.pluginDir, pluginDir, 'Expected pluginDir to be set correctly in .launchmap'); + // Stub workspace folder to point to temp directory + Object.defineProperty(vscode.workspace, 'workspaceFolders', { + get: () => [{ uri: vscode.Uri.file(tempDir) }], + configurable: true, }); - test('5๏ธโƒฃ Plugin Dir via .launchmap โ†’ Used in parser call', async () => { - const workspaceDir = path.join(tempDir, 'workspace'); - await fs.promises.mkdir(workspaceDir, { recursive: true }); - - vscode.workspace.updateWorkspaceFolders(0, 0, { uri: vscode.Uri.file(workspaceDir) }); - - const pluginDir = path.join(workspaceDir, 'plugins'); - await fs.promises.mkdir(pluginDir, { recursive: true }); - await fs.promises.writeFile(path.join(pluginDir, 'dummpy.py'), '# dummy plugin'); - - const configPath = path.join(workspaceDir, '.launchmap'); - const config = { pluginDir }; - await fs.promises.writeFile(configPath, JSON.stringify(config, null, 2)); + const doc = await vscode.workspace.openTextDocument(fixturePath); + await vscode.window.showTextDocument(doc); - const doc = await vscode.workspace.openTextDocument(fixturePath); - await vscode.window.showTextDocument(doc); + await vscode.commands.executeCommand('launchmap.openVisualizer'); - await vscode.commands.executeCommand('launchmap.openVisualizer'); + assert.ok(true, 'Visualizer executed with .launchmap pluginDir present'); - assert.ok(true, 'Visualizer executed with .launchmap pluginDir present'); + await fs.promises.unlink(configPath); + }); - await fs.promises.unlink(configPath); - }); - - suiteTeardown(async () => { - // Clean up temp directory after all tests - await rm(tempDir, { recursive: true, force: true }); - }); -}); \ No newline at end of file + suiteTeardown(async () => { + // Clean up temp directory after all tests + await rm(tempDir, { recursive: true, force: true }); + }); +}); diff --git a/src/test/fixtures/test.launch.py b/src/test/fixtures/test.launch.py index 387989a..b4efa2f 100644 --- a/src/test/fixtures/test.launch.py +++ b/src/test/fixtures/test.launch.py @@ -15,7 +15,8 @@ from launch import LaunchDescription from launch_ros.actions import Node + def generate_launch_description(): - return LaunchDescription([ - Node(package='demo_nodes_cpp', executable='talker', name='talker') - ]) \ No newline at end of file + return LaunchDescription( + [Node(package="demo_nodes_cpp", executable="talker", name="talker")] + ) diff --git a/src/test/runTest.ts b/src/test/runTest.ts index 93ed7ad..92cf1cd 100644 --- a/src/test/runTest.ts +++ b/src/test/runTest.ts @@ -16,16 +16,16 @@ import * as path from 'path'; import { runTests } from '@vscode/test-electron'; async function main() { - try { - await runTests({ - extensionDevelopmentPath: path.resolve(__dirname, '../../'), - extensionTestsPath: path.resolve(__dirname, './e2e/index'), - launchArgs: ['--disable-extensions', '--log-level=error'] - }); - } catch (err) { - console.error('Failed to run tests', err); - process.exit(1); - } + try { + await runTests({ + extensionDevelopmentPath: path.resolve(__dirname, '../../'), + extensionTestsPath: path.resolve(__dirname, './e2e/index'), + launchArgs: ['--disable-extensions', '--log-level=error'] + }); + } catch (err) { + console.error('Failed to run tests', err); + process.exit(1); + } } -main(); \ No newline at end of file +main(); diff --git a/src/ui/pluginDirStatusBar.ts b/src/ui/pluginDirStatusBar.ts index ee5cf87..0793209 100644 --- a/src/ui/pluginDirStatusBar.ts +++ b/src/ui/pluginDirStatusBar.ts @@ -19,23 +19,23 @@ import * as path from 'path'; let statusBarItem: vscode.StatusBarItem | undefined; export async function initPluginDirStatusBar() { - if (!statusBarItem) { - statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100); - statusBarItem.command = 'launchmap.setPluginDir'; - statusBarItem.tooltip = 'LaunchMap Plugin Directory'; - } + if (!statusBarItem) { + statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100); + statusBarItem.command = 'launchmap.setPluginDir'; + statusBarItem.tooltip = 'LaunchMap Plugin Directory'; + } - await updatePluginDirStatusBar(); - statusBarItem.show(); + await updatePluginDirStatusBar(); + statusBarItem.show(); } export async function updatePluginDirStatusBar() { - const pluginDir = await getPluginDir(); + const pluginDir = await getPluginDir(); - if (pluginDir) { - const label = path.basename(pluginDir); + if (pluginDir) { + const label = path.basename(pluginDir); statusBarItem!.text = `๐Ÿ“ฆ Plugin: ${label}`; - } else { + } else { statusBarItem!.text = '๐Ÿ“ฆ No Plugin Dir'; - } -} \ No newline at end of file + } +} diff --git a/src/utils/launchmapConfig.ts b/src/utils/launchmapConfig.ts index 9b7f011..ffbfeaa 100644 --- a/src/utils/launchmapConfig.ts +++ b/src/utils/launchmapConfig.ts @@ -19,30 +19,30 @@ import * as fs from 'fs/promises'; const CONFIG_FILENAME = '.launchmap'; export async function getPluginDir(): Promise { - const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; - if (!workspaceFolder) return null; - - const configPath = path.join(workspaceFolder.uri.fsPath, CONFIG_FILENAME); - - try { - const content = await fs.readFile(configPath, 'utf-8'); - const json = JSON.parse(content); - return json.pluginDir || null; - } catch { - return null; - } + const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; + if (!workspaceFolder) return null; + + const configPath = path.join(workspaceFolder.uri.fsPath, CONFIG_FILENAME); + + try { + const content = await fs.readFile(configPath, 'utf-8'); + const json = JSON.parse(content); + return json.pluginDir || null; + } catch { + return null; + } } export async function setPluginDir(pluginDir: string): Promise { - const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; - if (!workspaceFolder) { - vscode.window.showErrorMessage("No workspace folder found to save plugin directory."); - return; - } - - const configPath = path.join(workspaceFolder.uri.fsPath, CONFIG_FILENAME); - const config = { pluginDir }; - - await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8'); - vscode.window.showInformationMessage(`Plugin directory saved to ${CONFIG_FILENAME}`); -} \ No newline at end of file + const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; + if (!workspaceFolder) { + vscode.window.showErrorMessage('No workspace folder found to save plugin directory.'); + return; + } + + const configPath = path.join(workspaceFolder.uri.fsPath, CONFIG_FILENAME); + const config = { pluginDir }; + + await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8'); + vscode.window.showInformationMessage(`Plugin directory saved to ${CONFIG_FILENAME}`); +} diff --git a/webview/components/renderArguments.js b/webview/components/renderArguments.js index f6f94f4..c7f6419 100644 --- a/webview/components/renderArguments.js +++ b/webview/components/renderArguments.js @@ -16,26 +16,26 @@ import { renderBaseBlock } from './renderBaseBlock.js'; import { renderSection } from './renderSection.js'; export function renderArguments(container, argumentsList, options) { - if (!argumentsList || argumentsList.length === 0) return; + if (!argumentsList || argumentsList.length === 0) return; - argumentsList.forEach((arg, idx) => { - const path = `${options.pathPrefix || "arguments"}[${idx}]`; - const block = renderArgument(arg, { ...options, path }); + argumentsList.forEach((arg, idx) => { + const path = `${options.pathPrefix || 'arguments'}[${idx}]`; + const block = renderArgument(arg, { ...options, path }); - container.appendChild(block); - options.renderBlock(block, "argument"); - }); + container.appendChild(block); + options.renderBlock(block, 'argument'); + }); } export function renderArgument(arg, options) { - const block = renderBaseBlock({ type: "argument", options }); + const block = renderBaseBlock({ type: 'argument', options }); - const value = arg.default_value !== undefined ? arg.default_value : ""; - const argSection = renderSection("argument", "๐Ÿš€", arg.name, value, - { includeRightPort: true, portIdPrefix: `argument:${arg.name}`, portRegistry: options.portRegistry }); - block.appendChild(argSection); + const value = arg.default_value !== undefined ? arg.default_value : ''; + const argSection = renderSection('argument', '๐Ÿš€', arg.name, value, + { includeRightPort: true, portIdPrefix: `argument:${arg.name}`, portRegistry: options.portRegistry }); + block.appendChild(argSection); - block.dataset.argument = arg.name; + block.dataset.argument = arg.name; - return block; -} \ No newline at end of file + return block; +} diff --git a/webview/components/renderAutoResizableBody.js b/webview/components/renderAutoResizableBody.js index 03102f4..3ccbe6c 100644 --- a/webview/components/renderAutoResizableBody.js +++ b/webview/components/renderAutoResizableBody.js @@ -12,43 +12,43 @@ // See the License for the specific language governing permissions and // limitations under the License. -export function renderAutoResizableBody(container, type = "block", fixedSelectors = []) { - requestAnimationFrame(() => { - let offsetY = 0; - - if (fixedSelectors.length > 0) { - fixedSelectors.forEach(sel => { - const header = container.querySelector(sel); - if (header) { - const height = header.getBoundingClientRect().height; - offsetY = Math.max(offsetY, height + 45); - } - }); - } +export function renderAutoResizableBody(container, type = 'block', fixedSelectors = []) { + requestAnimationFrame(() => { + let offsetY = 0; - const blockSelector = `[class$="-${type}"]` - const allSelectors = [blockSelector, ...fixedSelectors].join(", "); - const children = container.querySelectorAll(allSelectors); - - let maxRight = 0; - let maxBottom = 0; - - children.forEach(child => { - const rect = child.getBoundingClientRect(); - const parentRect = container.getBoundingClientRect(); - const right = rect.right - parentRect.left; - const bottom = rect.bottom - parentRect.top; - - if (right > maxRight) maxRight = right; - if (bottom > maxBottom) maxBottom = bottom; - }); - - container.style.width = `${maxRight + 20}px`; - container.style.height = `${maxBottom + 20}px`; - - const body = container.querySelector(`[class$="-body"]`); - if (body) { - body.style.top = `${offsetY}px`; + if (fixedSelectors.length > 0) { + fixedSelectors.forEach(sel => { + const header = container.querySelector(sel); + if (header) { + const height = header.getBoundingClientRect().height; + offsetY = Math.max(offsetY, height + 45); } + }); + } + + const blockSelector = `[class$="-${type}"]`; + const allSelectors = [blockSelector, ...fixedSelectors].join(', '); + const children = container.querySelectorAll(allSelectors); + + let maxRight = 0; + let maxBottom = 0; + + children.forEach(child => { + const rect = child.getBoundingClientRect(); + const parentRect = container.getBoundingClientRect(); + const right = rect.right - parentRect.left; + const bottom = rect.bottom - parentRect.top; + + if (right > maxRight) maxRight = right; + if (bottom > maxBottom) maxBottom = bottom; }); -} \ No newline at end of file + + container.style.width = `${maxRight + 20}px`; + container.style.height = `${maxBottom + 20}px`; + + const body = container.querySelector('[class$="-body"]'); + if (body) { + body.style.top = `${offsetY}px`; + } + }); +} diff --git a/webview/components/renderBaseBlock.js b/webview/components/renderBaseBlock.js index 560a07e..2cf623f 100644 --- a/webview/components/renderBaseBlock.js +++ b/webview/components/renderBaseBlock.js @@ -12,54 +12,54 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { makeDraggable } from "../utils/drag.js"; -import { getTypeLabel } from "../utils/labels.js"; -import { renderEventPortRow } from "./renderEventPortRow.js"; +import { makeDraggable } from '../utils/drag.js'; +import { getTypeLabel } from '../utils/labels.js'; +import { renderEventPortRow } from './renderEventPortRow.js'; export function renderBaseBlock({ type, options }) { - const block = document.createElement("div"); - block.className = `block ${type}-block`; - block.style.position = "absolute"; + const block = document.createElement('div'); + block.className = `block ${type}-block`; + block.style.position = 'absolute'; - // Header container - const header = document.createElement("div"); - header.className = "block-header"; + // Header container + const header = document.createElement('div'); + header.className = 'block-header'; - // Top label - const heading = document.createElement("div"); - heading.className = "block-title"; - heading.innerText = getTypeLabel(type); - header.appendChild(heading); + // Top label + const heading = document.createElement('div'); + heading.className = 'block-title'; + heading.innerText = getTypeLabel(type); + header.appendChild(heading); - // Event ports - const path = options?.path; - const portRegistry = options?.portRegistry; - if (options?.events?.triggered_by?.length || options?.events?.triggers?.length) { - const leftLabel = options.eventLabels?.left || "โ† triggered by"; - const rightLabel = options.eventLabels?.right || "triggers โ†’"; + // Event ports + const path = options?.path; + const portRegistry = options?.portRegistry; + if (options?.events?.triggered_by?.length || options?.events?.triggers?.length) { + const leftLabel = options.eventLabels?.left || 'โ† triggered by'; + const rightLabel = options.eventLabels?.right || 'triggers โ†’'; - const eventPortRow = renderEventPortRow(path, portRegistry, leftLabel, rightLabel); - header.appendChild(eventPortRow); - } + const eventPortRow = renderEventPortRow(path, portRegistry, leftLabel, rightLabel); + header.appendChild(eventPortRow); + } - block.appendChild(header); + block.appendChild(header); - // Metadata - if (options?.path) { - block.dataset.path = options.path; - block.dataset.type = type; - } + // Metadata + if (options?.path) { + block.dataset.path = options.path; + block.dataset.type = type; + } - // Draggable - makeDraggable(block, { - ...options, - onDrag: () => { - if (options.renderEdges && options.parsedData) { - options.renderEdges(options.parsedData, options.portRegistry); - } - } - }); + // Draggable + makeDraggable(block, { + ...options, + onDrag: () => { + if (options.renderEdges && options.parsedData) { + options.renderEdges(options.parsedData, options.portRegistry); + } + } + }); - return block; + return block; } diff --git a/webview/components/renderComponent.js b/webview/components/renderComponent.js index ab4b0b8..c95fcb3 100644 --- a/webview/components/renderComponent.js +++ b/webview/components/renderComponent.js @@ -12,32 +12,32 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { getRenderer } from "../core/dispatcher.js"; +import { getRenderer } from '../core/dispatcher.js'; export function renderComponent(obj, container, options = {}) { - if (!obj || typeof obj !== "object") return; + if (!obj || typeof obj !== 'object') return; - const type = obj.type || detectType(obj); - const renderer = getRenderer(type); + const type = obj.type || detectType(obj); + const renderer = getRenderer(type); - if (!renderer) { - console.warn(`No renderer for type: ${type}`, obj); - return; - } + if (!renderer) { + console.warn(`No renderer for type: ${type}`, obj); + return; + } - renderer(obj, container, { - ...options, - renderComponent - }); + renderer(obj, container, { + ...options, + renderComponent + }); } function detectType(obj) { - if (obj.nodes || obj.groups || obj.composable_nodes) return "group"; - if (Array.isArray(obj.arguments)) return "arguments"; - if (Array.isArray(obj.includes)) return "includes"; - if (Array.isArray(obj.nodes)) return "nodes"; - if (Array.isArray(obj.opaque_functions)) return "opaque_functions"; - if (Array.isArray(obj.composable_nodes)) return "composable-nodes"; - if (Array.isArray(obj.composable_nodes_container)) return "composable-nodes-container"; - return null; -} \ No newline at end of file + if (obj.nodes || obj.groups || obj.composable_nodes) return 'group'; + if (Array.isArray(obj.arguments)) return 'arguments'; + if (Array.isArray(obj.includes)) return 'includes'; + if (Array.isArray(obj.nodes)) return 'nodes'; + if (Array.isArray(obj.opaque_functions)) return 'opaque_functions'; + if (Array.isArray(obj.composable_nodes)) return 'composable-nodes'; + if (Array.isArray(obj.composable_nodes_container)) return 'composable-nodes-container'; + return null; +} diff --git a/webview/components/renderComposableContainer.js b/webview/components/renderComposableContainer.js index 198b67f..15ab457 100644 --- a/webview/components/renderComposableContainer.js +++ b/webview/components/renderComposableContainer.js @@ -19,74 +19,74 @@ import { renderSection } from './renderSection.js'; import { LayoutManager } from '../core/layoutManager.js'; export function renderComposableContainerGroup(container, groups, options = {}) { - groups.forEach((group, idx) => { - const path = options.pathPrefix ? `${options.pathPrefix}.composable_nodes_container[${idx}]` : `composable_nodes_container[${idx}]`; - const block = renderComposableContainer(group, { ...options, path }); - - container.appendChild(block); - options.renderBlock(block, "composable-container"); - }); + groups.forEach((group, idx) => { + const path = options.pathPrefix ? `${options.pathPrefix}.composable_nodes_container[${idx}]` : `composable_nodes_container[${idx}]`; + const block = renderComposableContainer(group, { ...options, path }); + + container.appendChild(block); + options.renderBlock(block, 'composable-container'); + }); } export function renderComposableContainer(group, options = {}) { - const composableContainer = renderBaseBlock({ - type: "composable-container", - options: { - ...options, - events: group.events - } - }); + const composableContainer = renderBaseBlock({ + type: 'composable-container', + options: { + ...options, + events: group.events + } + }); - // Header - const header = document.createElement("div"); - header.className = "composable-container-header"; + // Header + const header = document.createElement('div'); + header.className = 'composable-container-header'; - // Render additional sections - const metaSections = [ - { key: "container", icon: "๐Ÿ“›", label: "Container", value: group.target_container }, - { key: "package", icon: "๐Ÿ“ฆ", label: "Package", value: group.package }, - { key: "executable", icon: "โ–ถ๏ธ", label: "Executable", value: group.executable }, - { key: "output", icon: "๐Ÿ–ฅ๏ธ", label: "Output", value: group.output} - ]; + // Render additional sections + const metaSections = [ + { key: 'container', icon: '๐Ÿ“›', label: 'Container', value: group.target_container }, + { key: 'package', icon: '๐Ÿ“ฆ', label: 'Package', value: group.package }, + { key: 'executable', icon: 'โ–ถ๏ธ', label: 'Executable', value: group.executable }, + { key: 'output', icon: '๐Ÿ–ฅ๏ธ', label: 'Output', value: group.output } + ]; - metaSections.forEach(( {key, icon, label, value }) => { - if (value) { - const section = renderSection(key, icon, label, value, { - includeLeftPort: true, - portIdPrefix: options.path, - portRegistry: options.portRegistry - }); - header.appendChild(section); - } - }); + metaSections.forEach(({ key, icon, label, value }) => { + if (value) { + const section = renderSection(key, icon, label, value, { + includeLeftPort: true, + portIdPrefix: options.path, + portRegistry: options.portRegistry + }); + header.appendChild(section); + } + }); - composableContainer.append(header); + composableContainer.append(header); - // Body - const body = document.createElement("div"); - body.className = "composable-container-body"; - composableContainer.appendChild(body); + // Body + const body = document.createElement('div'); + body.className = 'composable-container-body'; + composableContainer.appendChild(body); - const innerLayoutManager = new LayoutManager(20, 40, 80, 40); - const childOptions = { - ...options, - stopPropagation: true, - constrainToParent: true, - pathPrefix: `${options.path}`, - renderBlock: (block, columnType) => { - requestAnimationFrame(() => { - innerLayoutManager.placeBlock(block, columnType); - }) - } - }; + const innerLayoutManager = new LayoutManager(20, 40, 80, 40); + const childOptions = { + ...options, + stopPropagation: true, + constrainToParent: true, + pathPrefix: `${options.path}`, + renderBlock: (block, columnType) => { + requestAnimationFrame(() => { + innerLayoutManager.placeBlock(block, columnType); + }); + } + }; - // Render composable nodes - renderComponent( - { type: "composable_nodes", value: group.composable_nodes, namespace: group.namespace }, - body, childOptions - ); + // Render composable nodes + renderComponent( + { type: 'composable_nodes', value: group.composable_nodes, namespace: group.namespace }, + body, childOptions + ); - renderAutoResizableBody(composableContainer, "block", [".composable-container-header"]); + renderAutoResizableBody(composableContainer, 'block', ['.composable-container-header']); - return composableContainer; -} \ No newline at end of file + return composableContainer; +} diff --git a/webview/components/renderComposableNode.js b/webview/components/renderComposableNode.js index fae8e69..48250e6 100644 --- a/webview/components/renderComposableNode.js +++ b/webview/components/renderComposableNode.js @@ -16,40 +16,40 @@ import { renderSection } from './renderSection.js'; import { renderBaseBlock } from './renderBaseBlock.js'; export function renderComposableNodeGroup(container, nodes, options={}) { - nodes.forEach((node, idx) => { - const path = options.pathPrefix ? `${options.pathPrefix}.composable_nodes[${idx}]` : `composable_nodes[${idx}]`; - const block = renderComposableNode(node, { ...options, path }); - - container.appendChild(block); - options.renderBlock(block, "composable-node"); - }); + nodes.forEach((node, idx) => { + const path = options.pathPrefix ? `${options.pathPrefix}.composable_nodes[${idx}]` : `composable_nodes[${idx}]`; + const block = renderComposableNode(node, { ...options, path }); + + container.appendChild(block); + options.renderBlock(block, 'composable-node'); + }); } function renderComposableNode(node, options) { - const block = renderBaseBlock({ - type: 'composable-node', - options: { - ...options, - events: node.events - } - }) - - // Node name - const titleLabel = node.name || node.plugin || "(anonymous)"; - - // Sections - const renderOptions = { includeLeftPort: true, portIdPrefix: options.path, portRegistry: options.portRegistry }; - block.appendChild(renderSection("name", "๐Ÿ“›", "Name", titleLabel, renderOptions)); - block.appendChild(renderSection("package", "๐Ÿ“ฆ", "Package", node.package, renderOptions)); - block.appendChild(renderSection("plugin", "๐Ÿ”Œ", "Plugin", node.plugin, renderOptions)); - - if (node.parameters?.length > 0) { - block.appendChild(renderSection("parameters", "โš™๏ธ", "Params", node.parameters, renderOptions)); + const block = renderBaseBlock({ + type: 'composable-node', + options: { + ...options, + events: node.events } + }); - if (node.arguments?.length > 0) { - block.appendChild(renderSection("arguments", "๐Ÿ’ฌ", "Args", node.arguments, renderOptions)); - } + // Node name + const titleLabel = node.name || node.plugin || '(anonymous)'; + + // Sections + const renderOptions = { includeLeftPort: true, portIdPrefix: options.path, portRegistry: options.portRegistry }; + block.appendChild(renderSection('name', '๐Ÿ“›', 'Name', titleLabel, renderOptions)); + block.appendChild(renderSection('package', '๐Ÿ“ฆ', 'Package', node.package, renderOptions)); + block.appendChild(renderSection('plugin', '๐Ÿ”Œ', 'Plugin', node.plugin, renderOptions)); - return block; -} \ No newline at end of file + if (node.parameters?.length > 0) { + block.appendChild(renderSection('parameters', 'โš™๏ธ', 'Params', node.parameters, renderOptions)); + } + + if (node.arguments?.length > 0) { + block.appendChild(renderSection('arguments', '๐Ÿ’ฌ', 'Args', node.arguments, renderOptions)); + } + + return block; +} diff --git a/webview/components/renderEnvironmentVariables.js b/webview/components/renderEnvironmentVariables.js index 7a56727..f256b80 100644 --- a/webview/components/renderEnvironmentVariables.js +++ b/webview/components/renderEnvironmentVariables.js @@ -16,28 +16,28 @@ import { renderBaseBlock } from './renderBaseBlock.js'; import { renderSection } from './renderSection.js'; export function renderEnvironmentVariables(container, argumentsList, options) { - if (!argumentsList || argumentsList.length === 0) return; + if (!argumentsList || argumentsList.length === 0) return; - argumentsList.forEach((arg, idx) => { - const path = `${options.pathPrefix || "environment_variables"}[${idx}]`; - const block = renderEnvironmentVariable(arg, { ...options, path }); + argumentsList.forEach((arg, idx) => { + const path = `${options.pathPrefix || 'environment_variables'}[${idx}]`; + const block = renderEnvironmentVariable(arg, { ...options, path }); - container.appendChild(block); - options.renderBlock(block, "environment-variable"); - }); + container.appendChild(block); + options.renderBlock(block, 'environment-variable'); + }); } export function renderEnvironmentVariable(arg, options) { - const block = renderBaseBlock({ type: "environment-variable", options }); + const block = renderBaseBlock({ type: 'environment-variable', options }); - const value = arg.default_value !== undefined - ? arg.default_value - : (arg.value !== undefined ? arg.value : ""); - const argSection = renderSection("argument", "๐ŸŒ", arg.name, value, - { includeRightPort: true, portIdPrefix: `argument:${arg.name}`, portRegistry: options.portRegistry }); - block.appendChild(argSection); + const value = arg.default_value !== undefined + ? arg.default_value + : (arg.value !== undefined ? arg.value : ''); + const argSection = renderSection('argument', '๐ŸŒ', arg.name, value, + { includeRightPort: true, portIdPrefix: `argument:${arg.name}`, portRegistry: options.portRegistry }); + block.appendChild(argSection); - block.dataset.argument = arg.name; + block.dataset.argument = arg.name; - return block; -} \ No newline at end of file + return block; +} diff --git a/webview/components/renderEventHandler.js b/webview/components/renderEventHandler.js index 68e2833..b688caa 100644 --- a/webview/components/renderEventHandler.js +++ b/webview/components/renderEventHandler.js @@ -16,38 +16,38 @@ import { renderSection } from './renderSection.js'; import { renderBaseBlock } from './renderBaseBlock.js'; export function renderEventHandlerGroup(container, handlers, options={}) { - handlers.forEach((handler, idx) => { - const path = `events[${idx}]`; - const block = renderEventHandler(handler, { ...options, path }); + handlers.forEach((handler, idx) => { + const path = `events[${idx}]`; + const block = renderEventHandler(handler, { ...options, path }); - container.appendChild(block); - options.renderBlock(block, "event-handler"); - }); + container.appendChild(block); + options.renderBlock(block, 'event-handler'); + }); } function renderEventHandler(handler, options) { - const block = renderBaseBlock({ - type: "event-handler", - options: { - ...options, - events: { - triggered_by: handler.triggered_by, - triggers: handler.triggers - }, - eventLabels: handler.type === "OnProcessExit" - ? { left: "โ† target_action", right: "on_exit โ†’" } - : handler.type === "OnProcessStart" - ? { left: "โ† target_action", right: "on_start โ†’" } - : undefined - } - }); + const block = renderBaseBlock({ + type: 'event-handler', + options: { + ...options, + events: { + triggered_by: handler.triggered_by, + triggers: handler.triggers + }, + eventLabels: handler.type === 'OnProcessExit' + ? { left: 'โ† target_action', right: 'on_exit โ†’' } + : handler.type === 'OnProcessStart' + ? { left: 'โ† target_action', right: 'on_start โ†’' } + : undefined + } + }); - // Main Section - const type = handler.type || 'EventHandler'; - block.appendChild(renderSection("type", "๐Ÿ“ฃ", "Type", type, { - includeLeftPort: false, - includeRightPort: false - })); + // Main Section + const type = handler.type || 'EventHandler'; + block.appendChild(renderSection('type', '๐Ÿ“ฃ', 'Type', type, { + includeLeftPort: false, + includeRightPort: false + })); - return block; -} \ No newline at end of file + return block; +} diff --git a/webview/components/renderEventPortRow.js b/webview/components/renderEventPortRow.js index a9b9471..3ef7db0 100644 --- a/webview/components/renderEventPortRow.js +++ b/webview/components/renderEventPortRow.js @@ -12,44 +12,44 @@ // See the License for the specific language governing permissions and // limitations under the License. -export function renderEventPortRow(path, portRegistry, - leftLabelText = "โ† triggered by", rightLabelText = "triggers โ†’") { - const eventPortRow = document.createElement("div"); - eventPortRow.className = "event-port-row"; +export function renderEventPortRow(path, portRegistry, + leftLabelText = 'โ† triggered by', rightLabelText = 'triggers โ†’') { + const eventPortRow = document.createElement('div'); + eventPortRow.className = 'event-port-row'; - // Left port (triggered_by) - const leftWrapper = document.createElement("div"); - leftWrapper.className = "event-port-wrapper left"; + // Left port (triggered_by) + const leftWrapper = document.createElement('div'); + leftWrapper.className = 'event-port-wrapper left'; - const leftLabel = document.createElement("span"); - leftLabel.className = "event-label"; - leftLabel.innerText = leftLabelText; + const leftLabel = document.createElement('span'); + leftLabel.className = 'event-label'; + leftLabel.innerText = leftLabelText; - const leftPort = document.createElement("div"); - leftPort.className = "port left"; - leftWrapper.appendChild(leftPort); - leftWrapper.appendChild(leftLabel); + const leftPort = document.createElement('div'); + leftPort.className = 'port left'; + leftWrapper.appendChild(leftPort); + leftWrapper.appendChild(leftLabel); - eventPortRow.appendChild(leftWrapper); + eventPortRow.appendChild(leftWrapper); - if (portRegistry) portRegistry[`${path}.events.triggered_by`] = leftPort; + if (portRegistry) portRegistry[`${path}.events.triggered_by`] = leftPort; - // Right port (triggers) - const rightWrapper = document.createElement("div"); - rightWrapper.className = "event-port-wrapper right"; + // Right port (triggers) + const rightWrapper = document.createElement('div'); + rightWrapper.className = 'event-port-wrapper right'; - const rightLabel = document.createElement("span"); - rightLabel.className = "event-label"; - rightLabel.innerText = rightLabelText; + const rightLabel = document.createElement('span'); + rightLabel.className = 'event-label'; + rightLabel.innerText = rightLabelText; - const rightPort = document.createElement("div"); - rightPort.className = "port right"; - rightWrapper.appendChild(rightLabel); - rightWrapper.appendChild(rightPort); + const rightPort = document.createElement('div'); + rightPort.className = 'port right'; + rightWrapper.appendChild(rightLabel); + rightWrapper.appendChild(rightPort); - eventPortRow.appendChild(rightWrapper); - - if (portRegistry) portRegistry[`${path}.events.triggers`] = rightPort; + eventPortRow.appendChild(rightWrapper); - return eventPortRow; -} \ No newline at end of file + if (portRegistry) portRegistry[`${path}.events.triggers`] = rightPort; + + return eventPortRow; +} diff --git a/webview/components/renderGroup.js b/webview/components/renderGroup.js index edb45e9..2e8df6c 100644 --- a/webview/components/renderGroup.js +++ b/webview/components/renderGroup.js @@ -19,73 +19,73 @@ import { renderSection } from './renderSection.js'; import { LayoutManager } from '../core/layoutManager.js'; export function renderGroupGroup(container, groups, options = {}) { - groups.forEach((group, idx) => { - const path = options.pathPrefix ? `${options.pathPrefix}.groups[${idx}]` : `groups[${idx}]`; - const block = renderGroup(group, { ...options, path }); + groups.forEach((group, idx) => { + const path = options.pathPrefix ? `${options.pathPrefix}.groups[${idx}]` : `groups[${idx}]`; + const block = renderGroup(group, { ...options, path }); - container.appendChild(block); - options.renderBlock(block, "group"); - }); + container.appendChild(block); + options.renderBlock(block, 'group'); + }); } export function renderGroup(group, options = {}) { - const ns = group.namespace || ""; + const ns = group.namespace || ''; - const groupBox = renderBaseBlock({ - type: "group", - options: { - ...options, - events: group.events - } - }); - - // Header - const header = document.createElement("div"); - header.className = "group-header"; + const groupBox = renderBaseBlock({ + type: 'group', + options: { + ...options, + events: group.events + } + }); - // Render additional sections - const metaSections = [ - { key: "namespace", icon: "๐Ÿงญ", label: "Namespace", value: ns }, - { key: "condition", icon: "โ“", label: "Condition", value: group.condition } - ]; + // Header + const header = document.createElement('div'); + header.className = 'group-header'; - metaSections.forEach(( {key, icon, label, value }) => { - if (value) { - const section = renderSection(key, icon, label, value, { - includeLeftPort: true, - portIdPrefix: options.path, - portRegistry: options.portRegistry - }); - header.appendChild(section); - } - }); + // Render additional sections + const metaSections = [ + { key: 'namespace', icon: '๐Ÿงญ', label: 'Namespace', value: ns }, + { key: 'condition', icon: 'โ“', label: 'Condition', value: group.condition } + ]; - groupBox.append(header); + metaSections.forEach(({ key, icon, label, value }) => { + if (value) { + const section = renderSection(key, icon, label, value, { + includeLeftPort: true, + portIdPrefix: options.path, + portRegistry: options.portRegistry + }); + header.appendChild(section); + } + }); - // Body - const body = document.createElement("div"); - body.className = "group-body"; - groupBox.appendChild(body); + groupBox.append(header); - const innerLayoutManager = new LayoutManager(20, 40, 80, 40); - const childOptions = { - ...options, - stopPropagation: true, - constrainToParent: true, - pathPrefix: `${options.path}.actions`, - renderBlock: (block, columnType) => { - requestAnimationFrame(() => { - innerLayoutManager.placeBlock(block, columnType); - }); - } - }; + // Body + const body = document.createElement('div'); + body.className = 'group-body'; + groupBox.appendChild(body); - const actions = group.actions || {}; - for (const [key, value] of Object.entries(actions)) { - renderComponent({ type: key, value: value, namespace: ns }, body, childOptions); + const innerLayoutManager = new LayoutManager(20, 40, 80, 40); + const childOptions = { + ...options, + stopPropagation: true, + constrainToParent: true, + pathPrefix: `${options.path}.actions`, + renderBlock: (block, columnType) => { + requestAnimationFrame(() => { + innerLayoutManager.placeBlock(block, columnType); + }); } + }; - renderAutoResizableBody(groupBox, "block", [".group-header"]); + const actions = group.actions || {}; + for (const [key, value] of Object.entries(actions)) { + renderComponent({ type: key, value: value, namespace: ns }, body, childOptions); + } - return groupBox; -} \ No newline at end of file + renderAutoResizableBody(groupBox, 'block', ['.group-header']); + + return groupBox; +} diff --git a/webview/components/renderInclude.js b/webview/components/renderInclude.js index 1db03f0..19c271e 100644 --- a/webview/components/renderInclude.js +++ b/webview/components/renderInclude.js @@ -16,35 +16,35 @@ import { renderBaseBlock } from './renderBaseBlock.js'; import { renderSection } from './renderSection.js'; export function renderIncludesGroup(container, includes, options={}) { - includes.forEach((include, idx) => { - const path = options.pathPrefix ? `${options.pathPrefix}.includes[${idx}]` : `includes[${idx}]`; - const block = renderInclude(include, { ...options, path }); + includes.forEach((include, idx) => { + const path = options.pathPrefix ? `${options.pathPrefix}.includes[${idx}]` : `includes[${idx}]`; + const block = renderInclude(include, { ...options, path }); - container.appendChild(block); - options.renderBlock(block, "include"); - }); + container.appendChild(block); + options.renderBlock(block, 'include'); + }); } function renderInclude(include, options) { - const block = renderBaseBlock({ - type: 'include', - options: { - ...options, - events: include.events - } - }); - - // Render - const renderOptions = { includeLeftPort: true, portIdPrefix: options.path, portRegistry: options.portRegistry }; - const path = include.launch_description_source || ""; - block.appendChild(renderSection("launch_description_source", "๐Ÿ“‚", "Path", path, renderOptions)); - - const args = include.launch_arguments || {}; - block.appendChild(renderSection("launch_arguments", "๐Ÿ“ฅ", "Args", args, renderOptions)) - - if (include.condition) { - block.appendChild(renderSection("condition", "โ“", "Condition", include.condition, renderOptions)); + const block = renderBaseBlock({ + type: 'include', + options: { + ...options, + events: include.events } + }); - return block; -} \ No newline at end of file + // Render + const renderOptions = { includeLeftPort: true, portIdPrefix: options.path, portRegistry: options.portRegistry }; + const path = include.launch_description_source || ''; + block.appendChild(renderSection('launch_description_source', '๐Ÿ“‚', 'Path', path, renderOptions)); + + const args = include.launch_arguments || {}; + block.appendChild(renderSection('launch_arguments', '๐Ÿ“ฅ', 'Args', args, renderOptions)); + + if (include.condition) { + block.appendChild(renderSection('condition', 'โ“', 'Condition', include.condition, renderOptions)); + } + + return block; +} diff --git a/webview/components/renderNode.js b/webview/components/renderNode.js index ad2ad72..62f8323 100644 --- a/webview/components/renderNode.js +++ b/webview/components/renderNode.js @@ -16,45 +16,45 @@ import { renderSection } from './renderSection.js'; import { renderBaseBlock } from './renderBaseBlock.js'; export function renderNodeGroup(container, nodes, options={}) { - nodes.forEach((node, idx) => { - const path = options.pathPrefix ? `${options.pathPrefix}.nodes[${idx}]` : `nodes[${idx}]`; - const block = renderNode(node, { ...options, path }); + nodes.forEach((node, idx) => { + const path = options.pathPrefix ? `${options.pathPrefix}.nodes[${idx}]` : `nodes[${idx}]`; + const block = renderNode(node, { ...options, path }); - container.appendChild(block); - options.renderBlock(block, "node"); - }); + container.appendChild(block); + options.renderBlock(block, 'node'); + }); } function renderNode(node, options) { - const block = renderBaseBlock({ - type: 'node', - options: { - ...options, - events: node.events - } - }) - - // Node name - const titleLabel = node.name || node.executable || "(anonymous)"; - - // Sections - const renderOptions = { includeLeftPort: true, portIdPrefix: options.path, portRegistry: options.portRegistry }; - block.appendChild(renderSection("name", "๐Ÿ“›", "Name", titleLabel, renderOptions)); - block.appendChild(renderSection("package", "๐Ÿ“ฆ", "Package", node.package, renderOptions)); - block.appendChild(renderSection("executable", "โ–ถ๏ธ", "Executable", node.executable, renderOptions)); - block.appendChild(renderSection("output", "๐Ÿ–ฅ๏ธ", "Output", node.output || "โ€”", renderOptions)); - - if (node.condition) { - block.appendChild(renderSection("condition", "โ“", "Condition", node.condition, renderOptions)); + const block = renderBaseBlock({ + type: 'node', + options: { + ...options, + events: node.events } + }); - if (node.parameters?.length > 0) { - block.appendChild(renderSection("parameters", "โš™๏ธ", "Params", node.parameters, renderOptions)); - } + // Node name + const titleLabel = node.name || node.executable || '(anonymous)'; - if (node.arguments?.length > 0) { - block.appendChild(renderSection("arguments", "๐Ÿ’ฌ", "Args", node.arguments, renderOptions)); - } + // Sections + const renderOptions = { includeLeftPort: true, portIdPrefix: options.path, portRegistry: options.portRegistry }; + block.appendChild(renderSection('name', '๐Ÿ“›', 'Name', titleLabel, renderOptions)); + block.appendChild(renderSection('package', '๐Ÿ“ฆ', 'Package', node.package, renderOptions)); + block.appendChild(renderSection('executable', 'โ–ถ๏ธ', 'Executable', node.executable, renderOptions)); + block.appendChild(renderSection('output', '๐Ÿ–ฅ๏ธ', 'Output', node.output || 'โ€”', renderOptions)); + + if (node.condition) { + block.appendChild(renderSection('condition', 'โ“', 'Condition', node.condition, renderOptions)); + } - return block; -} \ No newline at end of file + if (node.parameters?.length > 0) { + block.appendChild(renderSection('parameters', 'โš™๏ธ', 'Params', node.parameters, renderOptions)); + } + + if (node.arguments?.length > 0) { + block.appendChild(renderSection('arguments', '๐Ÿ’ฌ', 'Args', node.arguments, renderOptions)); + } + + return block; +} diff --git a/webview/components/renderOpaqueFunction.js b/webview/components/renderOpaqueFunction.js index 2441c32..6fa8a4a 100644 --- a/webview/components/renderOpaqueFunction.js +++ b/webview/components/renderOpaqueFunction.js @@ -18,60 +18,60 @@ import { renderComponent } from './renderComponent.js'; import { LayoutManager } from '../core/layoutManager.js'; export function renderOpaqueFunctionGroup(container, opaqueFcns, options = {}) { - opaqueFcns.forEach((opaqueFcn, idx) => { - const path = options.pathPrefix ? `${options.pathPrefix}.opaque_functions[${idx}]` : `opaque_functions[${idx}]`; - const block = renderOpaqueFunction(opaqueFcn, { ...options, path }); + opaqueFcns.forEach((opaqueFcn, idx) => { + const path = options.pathPrefix ? `${options.pathPrefix}.opaque_functions[${idx}]` : `opaque_functions[${idx}]`; + const block = renderOpaqueFunction(opaqueFcn, { ...options, path }); - container.appendChild(block); - options.renderBlock(block, "opaque-function") - }); + container.appendChild(block); + options.renderBlock(block, 'opaque-function'); + }); } export function renderOpaqueFunction(opaqueFcn, options = {}) { - const fcnBox = renderBaseBlock({ - type: "opaque-function", - options: { - ...options, - events: opaqueFcn.events - } - }); - - // Header - const header = document.createElement("div"); - header.className = "opaque-function-header"; + const fcnBox = renderBaseBlock({ + type: 'opaque-function', + options: { + ...options, + events: opaqueFcn.events + } + }); - // Title - const title = document.createElement("div"); - title.className = "opaque-function-title"; - title.innerText = `${opaqueFcn.name}`; - header.appendChild(title); + // Header + const header = document.createElement('div'); + header.className = 'opaque-function-header'; - fcnBox.append(header); + // Title + const title = document.createElement('div'); + title.className = 'opaque-function-title'; + title.innerText = `${opaqueFcn.name}`; + header.appendChild(title); - // Body - const body = document.createElement("div"); - body.className = "opaque-function-body"; - fcnBox.appendChild(body); + fcnBox.append(header); - const innerLayoutManager = new LayoutManager(20, 40, 80, 40); - const childOptions = { - ...options, - stopPropagation: true, - constrainToParent: true, - pathPrefix: `${options.path}.returns`, - renderBlock: (block, columnType) => { - requestAnimationFrame(() => { - innerLayoutManager.placeBlock(block, columnType); - }) - } - }; + // Body + const body = document.createElement('div'); + body.className = 'opaque-function-body'; + fcnBox.appendChild(body); - const returns = opaqueFcn.returns || {}; - for (const [key, value] of Object.entries(returns)) { - renderComponent({ type: key, value: value }, body, childOptions); + const innerLayoutManager = new LayoutManager(20, 40, 80, 40); + const childOptions = { + ...options, + stopPropagation: true, + constrainToParent: true, + pathPrefix: `${options.path}.returns`, + renderBlock: (block, columnType) => { + requestAnimationFrame(() => { + innerLayoutManager.placeBlock(block, columnType); + }); } + }; - renderAutoResizableBody(fcnBox, "block", [".opaque-function-header"]); + const returns = opaqueFcn.returns || {}; + for (const [key, value] of Object.entries(returns)) { + renderComponent({ type: key, value: value }, body, childOptions); + } - return fcnBox; -} \ No newline at end of file + renderAutoResizableBody(fcnBox, 'block', ['.opaque-function-header']); + + return fcnBox; +} diff --git a/webview/components/renderPythonExpressions.js b/webview/components/renderPythonExpressions.js index 2fe9806..689bb76 100644 --- a/webview/components/renderPythonExpressions.js +++ b/webview/components/renderPythonExpressions.js @@ -16,41 +16,41 @@ import { renderBaseBlock } from './renderBaseBlock.js'; import { renderSection } from './renderSection.js'; export function renderPythonExpressions(container, expressionsList, options) { - if (!expressionsList || expressionsList.length === 0) return; - - expressionsList.forEach((expr, idx) => { - const path = `python_expressions[${idx}]`; - const block = renderPythonExpression(expr, { ...options, path }); - - container.appendChild(block); - options.renderBlock(block, "python-expression"); - }); + if (!expressionsList || expressionsList.length === 0) return; + + expressionsList.forEach((expr, idx) => { + const path = `python_expressions[${idx}]`; + const block = renderPythonExpression(expr, { ...options, path }); + + container.appendChild(block); + options.renderBlock(block, 'python-expression'); + }); } export function renderPythonExpression(expr, options) { - const block = renderBaseBlock({ - type: "python-expression", - options - }) - - // Code section - const codeSection = document.createElement("pre"); - codeSection.className = "python-code"; - codeSection.innerText = expr.body; - block.appendChild(codeSection); - - // Variables section (right port active) - if (expr.variables?.length > 0) { - expr.variables.forEach((v, i) => { - const varName = v.replace("${var:", "").replace("}", ""); - const varSection = renderSection("variable", "๐Ÿ“ค", varName, "", { - includeRightPort: true, - portIdPrefix: `variable:${varName}`, - portRegistry: options.portRegistry - }); - block.appendChild(varSection); - }); - } - - return block -} \ No newline at end of file + const block = renderBaseBlock({ + type: 'python-expression', + options + }); + + // Code section + const codeSection = document.createElement('pre'); + codeSection.className = 'python-code'; + codeSection.innerText = expr.body; + block.appendChild(codeSection); + + // Variables section (right port active) + if (expr.variables?.length > 0) { + expr.variables.forEach((v) => { + const varName = v.replace('${var:', '').replace('}', ''); + const varSection = renderSection('variable', '๐Ÿ“ค', varName, '', { + includeRightPort: true, + portIdPrefix: `variable:${varName}`, + portRegistry: options.portRegistry + }); + block.appendChild(varSection); + }); + } + + return block; +} diff --git a/webview/components/renderSection.js b/webview/components/renderSection.js index 2223e1b..dc73351 100644 --- a/webview/components/renderSection.js +++ b/webview/components/renderSection.js @@ -13,101 +13,101 @@ // limitations under the License. export function renderSection(fieldName, icon, label, contentValue, { - includeLeftPort = false, - includeRightPort = false, - portIdPrefix = "", - portRegistry = null + includeLeftPort = false, + includeRightPort = false, + portIdPrefix = '', + portRegistry = null }) { - const wrapper = document.createElement("div"); - wrapper.className = "section"; - wrapper.dataset.field = fieldName; + const wrapper = document.createElement('div'); + wrapper.className = 'section'; + wrapper.dataset.field = fieldName; - // Port ID - const fullPath = portIdPrefix - ? (fieldName ? `${portIdPrefix}.${fieldName}` : portIdPrefix) - : fieldName; + // Port ID + const fullPath = portIdPrefix + ? (fieldName ? `${portIdPrefix}.${fieldName}` : portIdPrefix) + : fieldName; - const content = document.createElement("div"); - content.className = "content"; + const content = document.createElement('div'); + content.className = 'content'; - // Try primitive - if (typeof contentValue !== "object" || contentValue === null) { - if (contentValue === "") { - content.innerHTML = `${icon} ${label}`; - } else { - content.innerHTML = `${icon} ${label}: ${escapeHtml(String(contentValue))}`; - } - // Left Port - if (includeLeftPort) { - const leftPort = document.createElement("div"); - leftPort.className = "port left"; - wrapper.appendChild(leftPort); - - if (portRegistry) portRegistry[fullPath] = leftPort; - } + // Try primitive + if (typeof contentValue !== 'object' || contentValue === null) { + if (contentValue === '') { + content.innerHTML = `${icon} ${label}`; + } else { + content.innerHTML = `${icon} ${label}: ${escapeHtml(String(contentValue))}`; + } + // Left Port + if (includeLeftPort) { + const leftPort = document.createElement('div'); + leftPort.className = 'port left'; + wrapper.appendChild(leftPort); - wrapper.appendChild(content); + if (portRegistry) portRegistry[fullPath] = leftPort; + } - // Right Port - if (includeRightPort) { - const rightPort = document.createElement("div"); - rightPort.className = "port right"; - wrapper.appendChild(rightPort); + wrapper.appendChild(content); - if (portRegistry) portRegistry[fullPath] = rightPort; - } + // Right Port + if (includeRightPort) { + const rightPort = document.createElement('div'); + rightPort.className = 'port right'; + wrapper.appendChild(rightPort); - return wrapper; + if (portRegistry) portRegistry[fullPath] = rightPort; } - // Complex data type - content.innerHTML = `${icon} ${label}`; - wrapper.classList.add("has-nested"); - wrapper.appendChild(content); - const nested = renderNestedValue(contentValue, fullPath, { - includeLeftPort, includeRightPort, portRegistry - }); - const nestedWrapper = document.createElement("div"); - nestedWrapper.className = "section-nested"; - nestedWrapper.appendChild(nested); - wrapper.appendChild(nestedWrapper); - return wrapper; + } + + // Complex data type + content.innerHTML = `${icon} ${label}`; + wrapper.classList.add('has-nested'); + wrapper.appendChild(content); + const nested = renderNestedValue(contentValue, fullPath, { + includeLeftPort, includeRightPort, portRegistry + }); + const nestedWrapper = document.createElement('div'); + nestedWrapper.className = 'section-nested'; + nestedWrapper.appendChild(nested); + wrapper.appendChild(nestedWrapper); + + return wrapper; } function renderNestedValue(value, basePath, options) { - const list = document.createElement("ul"); - list.style.margin = "4px 0"; - list.style.paddingLeft = "16px"; + const list = document.createElement('ul'); + list.style.margin = '4px 0'; + list.style.paddingLeft = '16px'; - if (Array.isArray(value)) { - value.forEach((item, idx) => { - const itemPath = `${basePath}[${idx}]`; - const li = document.createElement("li"); - li.appendChild(renderSection("", "", "", item, { - ...options, - portIdPrefix: itemPath - })); - list.appendChild(li); - }); - } else { - Object.entries(value).forEach(([key, val]) => { - const li = document.createElement("li"); - li.appendChild(renderSection(key, "", key, val, { - ...options, - portIdPrefix: basePath - })); - list.appendChild(li); - }); - } + if (Array.isArray(value)) { + value.forEach((item, idx) => { + const itemPath = `${basePath}[${idx}]`; + const li = document.createElement('li'); + li.appendChild(renderSection('', '', '', item, { + ...options, + portIdPrefix: itemPath + })); + list.appendChild(li); + }); + } else { + Object.entries(value).forEach(([key, val]) => { + const li = document.createElement('li'); + li.appendChild(renderSection(key, '', key, val, { + ...options, + portIdPrefix: basePath + })); + list.appendChild(li); + }); + } - return list; + return list; } function escapeHtml(text) { - return text - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """); -} \ No newline at end of file + return text + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); +} diff --git a/webview/components/renderTimerActions.js b/webview/components/renderTimerActions.js index 2bc77d3..dfd6254 100644 --- a/webview/components/renderTimerActions.js +++ b/webview/components/renderTimerActions.js @@ -19,67 +19,67 @@ import { renderSection } from './renderSection.js'; import { LayoutManager } from '../core/layoutManager.js'; export function renderTimerActions(container, timerActions, options = {}) { - timerActions.forEach((group, idx) => { - const path = options.pathPrefix ? `${options.pathPrefix}.timer_actions[${idx}]` : `timer_actions[${idx}]`; - const block = renderTimerAction(group, { ...options, path }); + timerActions.forEach((group, idx) => { + const path = options.pathPrefix ? `${options.pathPrefix}.timer_actions[${idx}]` : `timer_actions[${idx}]`; + const block = renderTimerAction(group, { ...options, path }); - container.appendChild(block); - options.renderBlock(block, "timer-action"); - }); + container.appendChild(block); + options.renderBlock(block, 'timer-action'); + }); } export function renderTimerAction(timerAction, options = {}) { - const timerActionBox = renderBaseBlock({ - type: "timer-action", - options - }); + const timerActionBox = renderBaseBlock({ + type: 'timer-action', + options + }); - // Header - const header = document.createElement("div"); - header.className = "timer-action-header"; + // Header + const header = document.createElement('div'); + header.className = 'timer-action-header'; - // Render additional sections - const metaSections = [ - { key: "period", icon: "๐Ÿงญ", label: "Period", value: timerAction.period }, - ]; + // Render additional sections + const metaSections = [ + { key: 'period', icon: '๐Ÿงญ', label: 'Period', value: timerAction.period }, + ]; - metaSections.forEach(( {key, icon, label, value }) => { - if (value) { - const section = renderSection(key, icon, label, value, { - includeLeftPort: true, - portIdPrefix: options.path, - portRegistry: options.portRegistry - }); - header.appendChild(section); - } - }); - - timerActionBox.append(header); + metaSections.forEach(({ key, icon, label, value }) => { + if (value) { + const section = renderSection(key, icon, label, value, { + includeLeftPort: true, + portIdPrefix: options.path, + portRegistry: options.portRegistry + }); + header.appendChild(section); + } + }); - // Body - const body = document.createElement("div"); - body.className = "timer-action-body"; - timerActionBox.appendChild(body); + timerActionBox.append(header); - const innerLayoutManager = new LayoutManager(20, 40, 80, 40); - const childOptions = { - ...options, - stopPropagation: true, - constrainToParent: true, - pathPrefix: `${options.path}.actions`, - renderBlock: (block, columnType) => { - requestAnimationFrame(() => { - innerLayoutManager.placeBlock(block, columnType); - }); - } - }; + // Body + const body = document.createElement('div'); + body.className = 'timer-action-body'; + timerActionBox.appendChild(body); - const actions = timerAction.actions || {}; - for (const [key, value] of Object.entries(actions)) { - renderComponent({ type: key, value: value }, body, childOptions); + const innerLayoutManager = new LayoutManager(20, 40, 80, 40); + const childOptions = { + ...options, + stopPropagation: true, + constrainToParent: true, + pathPrefix: `${options.path}.actions`, + renderBlock: (block, columnType) => { + requestAnimationFrame(() => { + innerLayoutManager.placeBlock(block, columnType); + }); } + }; - renderAutoResizableBody(timerActionBox, "block", [".timer-action-header"]); + const actions = timerAction.actions || {}; + for (const [key, value] of Object.entries(actions)) { + renderComponent({ type: key, value: value }, body, childOptions); + } - return timerActionBox; -} \ No newline at end of file + renderAutoResizableBody(timerActionBox, 'block', ['.timer-action-header']); + + return timerActionBox; +} diff --git a/webview/components/setupRenderers.js b/webview/components/setupRenderers.js index f2470b1..c56279e 100644 --- a/webview/components/setupRenderers.js +++ b/webview/components/setupRenderers.js @@ -12,59 +12,59 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { registerRenderer } from "../core/dispatcher.js"; -import { renderArguments } from "./renderArguments.js"; -import { renderIncludesGroup } from "./renderInclude.js"; -import { renderNodeGroup } from "./renderNode.js"; -import { renderGroupGroup } from "./renderGroup.js"; -import { renderOpaqueFunctionGroup } from "./renderOpaqueFunction.js"; -import { renderComposableContainerGroup } from "./renderComposableContainer.js"; -import { renderComposableNodeGroup } from "./renderComposableNode.js"; -import { renderEventHandlerGroup } from "./renderEventHandler.js"; -import { renderPythonExpressions } from "./renderPythonExpressions.js"; -import { renderEnvironmentVariables } from "./renderEnvironmentVariables.js"; -import { renderTimerActions } from "./renderTimerActions.js"; +import { registerRenderer } from '../core/dispatcher.js'; +import { renderArguments } from './renderArguments.js'; +import { renderIncludesGroup } from './renderInclude.js'; +import { renderNodeGroup } from './renderNode.js'; +import { renderGroupGroup } from './renderGroup.js'; +import { renderOpaqueFunctionGroup } from './renderOpaqueFunction.js'; +import { renderComposableContainerGroup } from './renderComposableContainer.js'; +import { renderComposableNodeGroup } from './renderComposableNode.js'; +import { renderEventHandlerGroup } from './renderEventHandler.js'; +import { renderPythonExpressions } from './renderPythonExpressions.js'; +import { renderEnvironmentVariables } from './renderEnvironmentVariables.js'; +import { renderTimerActions } from './renderTimerActions.js'; -registerRenderer("arguments", (obj, container, options) => { - renderArguments(container, obj.value || [], options); +registerRenderer('arguments', (obj, container, options) => { + renderArguments(container, obj.value || [], options); }); -registerRenderer("environment_variables", (obj, container, options) => { - renderEnvironmentVariables(container, obj.value || [], options); +registerRenderer('environment_variables', (obj, container, options) => { + renderEnvironmentVariables(container, obj.value || [], options); }); -registerRenderer("python_expressions", (obj, container, options) => { - renderPythonExpressions(container, obj.value || [], options); +registerRenderer('python_expressions', (obj, container, options) => { + renderPythonExpressions(container, obj.value || [], options); }); -registerRenderer("event_handlers", (obj, container, options) => { - renderEventHandlerGroup(container, obj.value || [], options); +registerRenderer('event_handlers', (obj, container, options) => { + renderEventHandlerGroup(container, obj.value || [], options); }); -registerRenderer("nodes", (obj, container, options) => { - renderNodeGroup(container, obj.value || [], options); +registerRenderer('nodes', (obj, container, options) => { + renderNodeGroup(container, obj.value || [], options); }); -registerRenderer("includes", (obj, container, options) => { - renderIncludesGroup(container, obj.value || [], options); +registerRenderer('includes', (obj, container, options) => { + renderIncludesGroup(container, obj.value || [], options); }); -registerRenderer("groups", (obj, container, options) => { - renderGroupGroup(container, obj.value || [], options); +registerRenderer('groups', (obj, container, options) => { + renderGroupGroup(container, obj.value || [], options); }); -registerRenderer("timer_actions", (obj, container, options) => { - renderTimerActions(container, obj.value || [], options); +registerRenderer('timer_actions', (obj, container, options) => { + renderTimerActions(container, obj.value || [], options); }); -registerRenderer("opaque_functions", (obj, container, options) => { - renderOpaqueFunctionGroup(container, obj.value || [], options); +registerRenderer('opaque_functions', (obj, container, options) => { + renderOpaqueFunctionGroup(container, obj.value || [], options); }); -registerRenderer("composable_nodes_container", (obj, container, options) => { - renderComposableContainerGroup(container, obj.value || [], options); +registerRenderer('composable_nodes_container', (obj, container, options) => { + renderComposableContainerGroup(container, obj.value || [], options); }); -registerRenderer("composable_nodes", (obj, container, options) => { - renderComposableNodeGroup(container, obj.value || [], options); -}); \ No newline at end of file +registerRenderer('composable_nodes', (obj, container, options) => { + renderComposableNodeGroup(container, obj.value || [], options); +}); diff --git a/webview/core/dispatcher.js b/webview/core/dispatcher.js index 68d9931..299353d 100644 --- a/webview/core/dispatcher.js +++ b/webview/core/dispatcher.js @@ -15,13 +15,13 @@ const registry = new Map(); export function registerRenderer(type, fn) { - registry.set(type, fn); + registry.set(type, fn); } export function getRenderer(type) { - return registry.get(type); + return registry.get(type); } export function getRegisteredRenderKeys() { - return Array.from(registry.keys()); -} \ No newline at end of file + return Array.from(registry.keys()); +} diff --git a/webview/core/layoutManager.js b/webview/core/layoutManager.js index 3e2ae3d..cc94ae5 100644 --- a/webview/core/layoutManager.js +++ b/webview/core/layoutManager.js @@ -13,82 +13,81 @@ // limitations under the License. export class LayoutManager { - constructor(startX = 100, startY = 100, columnSpacing = 350, rowSpacing = 40) { - this.startX = startX; - this.startY = startY; - this.columnSpacing = columnSpacing; - this.rowSpacing = rowSpacing; - this.columns = []; + constructor(startX = 100, startY = 100, columnSpacing = 350, rowSpacing = 40) { + this.startX = startX; + this.startY = startY; + this.columnSpacing = columnSpacing; + this.rowSpacing = rowSpacing; + this.columns = []; - this.typeToColumn = { - "argument": 0, - "environment-variable": 0, - "python-expression": 0, - "event-handler": 1, - "node": 2, - "composable-node": 2, - "include": 2, - "group": 3, - "timer-action": 3, - "composable-container": 3, - "opaque-function": 3 - }; - } - - getColumnIndexForType(type) { - return this.typeToColumn[type] ?? 0; - } + this.typeToColumn = { + 'argument': 0, + 'environment-variable': 0, + 'python-expression': 0, + 'event-handler': 1, + 'node': 2, + 'composable-node': 2, + 'include': 2, + 'group': 3, + 'timer-action': 3, + 'composable-container': 3, + 'opaque-function': 3 + }; + } - ensureColumn(columnIndex) { - // Build all missing columns up to columnIndex - for (let i = 0; i <= columnIndex; i++) { - if (!this.columns[i]) { - const prev = this.columns[i-1]; - this.columns[i] = { - x: prev ? prev.x + prev.maxWidth + this.columnSpacing : this.startX, - y: this.startY, - maxWidth: 0, - blocks: [] - }; - } - } + getColumnIndexForType(type) { + return this.typeToColumn[type] ?? 0; + } - return this.columns[columnIndex]; + ensureColumn(columnIndex) { + // Build all missing columns up to columnIndex + for (let i = 0; i <= columnIndex; i++) { + if (!this.columns[i]) { + const prev = this.columns[i-1]; + this.columns[i] = { + x: prev ? prev.x + prev.maxWidth + this.columnSpacing : this.startX, + y: this.startY, + maxWidth: 0, + blocks: [] + }; + } } - placeBlock(block, type) { - const columnIndex = this.getColumnIndexForType(type); - const col = this.ensureColumn(columnIndex); - const rect = block.getBoundingClientRect(); + return this.columns[columnIndex]; + } + + placeBlock(block, type) { + const columnIndex = this.getColumnIndexForType(type); + const col = this.ensureColumn(columnIndex); + const rect = block.getBoundingClientRect(); - block.style.left = `${col.x}px`; - block.style.top = `${col.y}px`; + block.style.left = `${col.x}px`; + block.style.top = `${col.y}px`; - col.blocks.push(block); + col.blocks.push(block); - col.y += rect.height + this.rowSpacing; - if (rect.width > col.maxWidth) { - col.maxWidth = rect.width; - this.reflowColumnsFrom(columnIndex + 1); - } + col.y += rect.height + this.rowSpacing; + if (rect.width > col.maxWidth) { + col.maxWidth = rect.width; + this.reflowColumnsFrom(columnIndex + 1); } + } - reflowColumnsFrom(startIndex) { - for (let i = startIndex; i < this.columns.length; i++) { - const prev = this.columns[i - 1]; - const curr = this.columns[i]; - curr.x = prev.x + prev.maxWidth + this.columnSpacing; + reflowColumnsFrom(startIndex) { + for (let i = startIndex; i < this.columns.length; i++) { + const prev = this.columns[i - 1]; + const curr = this.columns[i]; + curr.x = prev.x + prev.maxWidth + this.columnSpacing; - curr.blocks.forEach(block => { - const rect = block.getBoundingClientRect(); - const top = parseInt(block.style.top, 10); - block.style.left = `${curr.x}px`; - block.style.top = `${top}px`; - }); - } + curr.blocks.forEach(block => { + const top = parseInt(block.style.top, 10); + block.style.left = `${curr.x}px`; + block.style.top = `${top}px`; + }); } + } - nextColumnIndex() { - return this.columns.length; - } -} \ No newline at end of file + nextColumnIndex() { + return this.columns.length; + } +} diff --git a/webview/core/registry.js b/webview/core/registry.js index e31ef75..08b4080 100644 --- a/webview/core/registry.js +++ b/webview/core/registry.js @@ -13,5 +13,5 @@ // limitations under the License. export const registrySystem = { - portRegistry: {}, -} \ No newline at end of file + portRegistry: {}, +}; diff --git a/webview/core/renderAll.js b/webview/core/renderAll.js index b6ad80f..4f404b4 100644 --- a/webview/core/renderAll.js +++ b/webview/core/renderAll.js @@ -12,55 +12,55 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { registrySystem } from "./registry.js"; -import { renderComponent } from "../components/renderComponent.js"; -import { enableZoomAndPan } from "./zoomPanController.js"; -import { renderEdges } from "./renderEdges.js"; -import { getRegisteredRenderKeys } from "./dispatcher.js"; -import { LayoutManager } from "./layoutManager.js"; -import { autoFitToScreen } from "../utils/autoFitToScreen.js"; +import { registrySystem } from './registry.js'; +import { renderComponent } from '../components/renderComponent.js'; +import { enableZoomAndPan } from './zoomPanController.js'; +import { renderEdges } from './renderEdges.js'; +import { getRegisteredRenderKeys } from './dispatcher.js'; +import { LayoutManager } from './layoutManager.js'; +import { autoFitToScreen } from '../utils/autoFitToScreen.js'; export function renderAll(data) { - const editor = document.getElementById("editor"); - editor.innerHTML = ""; + const editor = document.getElementById('editor'); + editor.innerHTML = ''; - // Create zoom layer - const zoomLayer = document.createElement("div"); - zoomLayer.id = "zoom-layer"; - editor.appendChild(zoomLayer); + // Create zoom layer + const zoomLayer = document.createElement('div'); + zoomLayer.id = 'zoom-layer'; + editor.appendChild(zoomLayer); - // Edges - const edgeLayer = document.createElementNS("http://www.w3.org/2000/svg", "svg"); - edgeLayer.setAttribute("id", "edge-layer"); - edgeLayer.classList.add("edge-layer"); - zoomLayer.appendChild(edgeLayer); + // Edges + const edgeLayer = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + edgeLayer.setAttribute('id', 'edge-layer'); + edgeLayer.classList.add('edge-layer'); + zoomLayer.appendChild(edgeLayer); - // Render - const layoutManager = new LayoutManager(); - const context = { - parsedData: data, - portRegistry: registrySystem.portRegistry, - renderEdges, - renderBlock: (block, columnType) => { - requestAnimationFrame(() => { - layoutManager.placeBlock(block, columnType); - }); - } - }; + // Render + const layoutManager = new LayoutManager(); + const context = { + parsedData: data, + portRegistry: registrySystem.portRegistry, + renderEdges, + renderBlock: (block, columnType) => { + requestAnimationFrame(() => { + layoutManager.placeBlock(block, columnType); + }); + } + }; - const renderKeys = getRegisteredRenderKeys(); - for (const key of Object.keys(data)) { - if (renderKeys.includes(key)) { - const value = data[key]; - const typeHint = Array.isArray(value) ? key : value.type || key; - renderComponent({ value: value, type: typeHint }, zoomLayer, context); - } + const renderKeys = getRegisteredRenderKeys(); + for (const key of Object.keys(data)) { + if (renderKeys.includes(key)) { + const value = data[key]; + const typeHint = Array.isArray(value) ? key : value.type || key; + renderComponent({ value: value, type: typeHint }, zoomLayer, context); } + } + + requestAnimationFrame(() => { + autoFitToScreen(editor, zoomLayer); + renderEdges(data, registrySystem.portRegistry); + }); - requestAnimationFrame(() => { - autoFitToScreen(editor, zoomLayer); - renderEdges(data, registrySystem.portRegistry); - }); - - enableZoomAndPan(editor, zoomLayer, () => renderEdges(data, registrySystem.portRegistry)); -} \ No newline at end of file + enableZoomAndPan(editor, zoomLayer, () => renderEdges(data, registrySystem.portRegistry)); +} diff --git a/webview/core/renderEdges.js b/webview/core/renderEdges.js index bec2c33..39faf44 100644 --- a/webview/core/renderEdges.js +++ b/webview/core/renderEdges.js @@ -13,76 +13,76 @@ // limitations under the License. export function renderEdges(data, portRegistry) { - document.getElementById("edge-layer").innerHTML = ""; - - (data.launch_argument_usages || []).forEach(usage => { - const fromPortId = `argument:${usage.argument}.argument`; - const toPortId = `${usage.path}`; - connectPorts(fromPortId, toPortId, portRegistry); - }); - - (data.environment_variable_usages || []).forEach(usage => { - const fromPortId = `argument:${usage.argument}.argument`; - const toPortId = `${usage.path}`; - connectPorts(fromPortId, toPortId, portRegistry); - }); - - (data.python_expression_usages || []).forEach(usage => { - const fromPortId = `variable:${usage.variable}.variable`; - const toPortId = `${usage.path}`; - connectPorts(fromPortId, toPortId, portRegistry); - }); - - (data.event_handlers || []).forEach((handler, index) => { - const triggersPortId = `${handler.triggered_by[0]}`; - const eventTriggeredPortId = `events[${index}].events.triggered_by`; - connectPorts(triggersPortId, eventTriggeredPortId, portRegistry, "event-handler"); - - const eventTriggersPortId = `events[${index}].events.triggers`; - const triggeredPortId = `${handler.triggers[0]}`; - connectPorts(eventTriggersPortId, triggeredPortId, portRegistry, "event-handler"); - }); + document.getElementById('edge-layer').innerHTML = ''; + + (data.launch_argument_usages || []).forEach(usage => { + const fromPortId = `argument:${usage.argument}.argument`; + const toPortId = `${usage.path}`; + connectPorts(fromPortId, toPortId, portRegistry); + }); + + (data.environment_variable_usages || []).forEach(usage => { + const fromPortId = `argument:${usage.argument}.argument`; + const toPortId = `${usage.path}`; + connectPorts(fromPortId, toPortId, portRegistry); + }); + + (data.python_expression_usages || []).forEach(usage => { + const fromPortId = `variable:${usage.variable}.variable`; + const toPortId = `${usage.path}`; + connectPorts(fromPortId, toPortId, portRegistry); + }); + + (data.event_handlers || []).forEach((handler, index) => { + const triggersPortId = `${handler.triggered_by[0]}`; + const eventTriggeredPortId = `events[${index}].events.triggered_by`; + connectPorts(triggersPortId, eventTriggeredPortId, portRegistry, 'event-handler'); + + const eventTriggersPortId = `events[${index}].events.triggers`; + const triggeredPortId = `${handler.triggers[0]}`; + connectPorts(eventTriggersPortId, triggeredPortId, portRegistry, 'event-handler'); + }); } -function connectPorts(fromPortId, toPortId, portRegistry, className="") { - const fromPort = portRegistry[fromPortId]; - const toPort = portRegistry[toPortId]; +function connectPorts(fromPortId, toPortId, portRegistry, className='') { + const fromPort = portRegistry[fromPortId]; + const toPort = portRegistry[toPortId]; - if (!fromPort || !toPort) return; + if (!fromPort || !toPort) return; - fromPort.classList.add("used-port"); - toPort.classList.add("used-port"); + fromPort.classList.add('used-port'); + toPort.classList.add('used-port'); - const from = getCenter(fromPort); - const to = getCenter(toPort); + const from = getCenter(fromPort); + const to = getCenter(toPort); - drawEdge(from, to, className); + drawEdge(from, to, className); } function getCenter(el) { - const zoomLayer = document.getElementById("zoom-layer"); - const zoomRect = zoomLayer.getBoundingClientRect(); - const rect = el.getBoundingClientRect(); + const zoomLayer = document.getElementById('zoom-layer'); + const zoomRect = zoomLayer.getBoundingClientRect(); + const rect = el.getBoundingClientRect(); - const zoom = window.zoomState?.scale || 1; + const zoom = window.zoomState?.scale || 1; - return { - x: (rect.left - zoomRect.left + rect.width / 2) / zoom , - y: (rect.top - zoomRect.top + rect.height / 2) / zoom - }; + return { + x: (rect.left - zoomRect.left + rect.width / 2) / zoom, + y: (rect.top - zoomRect.top + rect.height / 2) / zoom + }; } function drawEdge(from, to, className) { - const svg = document.getElementById("edge-layer"); - const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); + const svg = document.getElementById('edge-layer'); + const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); - const dx = Math.abs(to.x - from.x); - const controlOffset = Math.min(100, dx / 2); + const dx = Math.abs(to.x - from.x); + const controlOffset = Math.min(100, dx / 2); - const d = `M ${from.x},${from.y} C ${from.x + controlOffset},${from.y} ${to.x - controlOffset},${to.y} ${to.x},${to.y}`; + const d = `M ${from.x},${from.y} C ${from.x + controlOffset},${from.y} ${to.x - controlOffset},${to.y} ${to.x},${to.y}`; - path.setAttribute("d", d); - path.setAttribute("class", `edge-line ${className}`.trim()); + path.setAttribute('d', d); + path.setAttribute('class', `edge-line ${className}`.trim()); - svg.appendChild(path); + svg.appendChild(path); } diff --git a/webview/core/zoomPanController.js b/webview/core/zoomPanController.js index aae6dcc..61d2097 100644 --- a/webview/core/zoomPanController.js +++ b/webview/core/zoomPanController.js @@ -13,62 +13,62 @@ // limitations under the License. export function enableZoomAndPan(editor, zoomLayer, renderEdgesCallback) { - const zoomState = { - scale: 1, - offsetX: 0, - offsetY: 0 - }; - window.zoomState = zoomState; + const zoomState = { + scale: 1, + offsetX: 0, + offsetY: 0 + }; + window.zoomState = zoomState; - let isPanning = false; - let startX, startY; + let isPanning = false; + let startX, startY; - const ZOOM_MIN = 0.2; - const ZOOM_MAX = 3; + const ZOOM_MIN = 0.2; + const ZOOM_MAX = 3; - function applyTransform() { - zoomLayer.style.transform = `translate(${zoomState.offsetX}px, ${zoomState.offsetY}px) scale(${zoomState.scale})`; - if (renderEdgesCallback) renderEdgesCallback(); - } + function applyTransform() { + zoomLayer.style.transform = `translate(${zoomState.offsetX}px, ${zoomState.offsetY}px) scale(${zoomState.scale})`; + if (renderEdgesCallback) renderEdgesCallback(); + } - // Shift + Drag to pan - editor.addEventListener('mousedown', (e) => { - if (e.shiftKey) { - isPanning = true; - startX = e.clientX; - startY = e.clientY; - e.preventDefault(); - } - }); + // Shift + Drag to pan + editor.addEventListener('mousedown', (e) => { + if (e.shiftKey) { + isPanning = true; + startX = e.clientX; + startY = e.clientY; + e.preventDefault(); + } + }); - window.addEventListener('mousemove', (e) => { - if (!isPanning) return; - zoomState.offsetX += e.clientX - startX; - zoomState.offsetY += e.clientY - startY; - startX = e.clientX; - startY = e.clientY; - applyTransform(); - }); + window.addEventListener('mousemove', (e) => { + if (!isPanning) return; + zoomState.offsetX += e.clientX - startX; + zoomState.offsetY += e.clientY - startY; + startX = e.clientX; + startY = e.clientY; + applyTransform(); + }); - window.addEventListener('mouseup', () => isPanning = false); + window.addEventListener('mouseup', () => isPanning = false); - // Scroll to zoom - editor.addEventListener("wheel", (e) => { - if (!e.ctrlKey && !e.shiftKey) { - e.preventDefault(); - const zoomFactor = 1 - e.deltaY * 0.001; - const newZoom = Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, zoomState.scale * zoomFactor)); + // Scroll to zoom + editor.addEventListener('wheel', (e) => { + if (!e.ctrlKey && !e.shiftKey) { + e.preventDefault(); + const zoomFactor = 1 - e.deltaY * 0.001; + const newZoom = Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, zoomState.scale * zoomFactor)); - const rect = zoomLayer.getBoundingClientRect(); - const dx = (e.clientX - rect.left - zoomState.offsetX) / zoomState.scale; - const dy = (e.clientY - rect.top - zoomState.offsetY) / zoomState.scale; + const rect = zoomLayer.getBoundingClientRect(); + const dx = (e.clientX - rect.left - zoomState.offsetX) / zoomState.scale; + const dy = (e.clientY - rect.top - zoomState.offsetY) / zoomState.scale; - zoomState.offsetX -= dx * (newZoom - zoomState.scale); - zoomState.offsetY -= dy * (newZoom - zoomState.scale); - zoomState.scale = newZoom; - applyTransform(); - } - }, { passive: false }); + zoomState.offsetX -= dx * (newZoom - zoomState.scale); + zoomState.offsetY -= dy * (newZoom - zoomState.scale); + zoomState.scale = newZoom; + applyTransform(); + } + }, { passive: false }); - applyTransform(); -} \ No newline at end of file + applyTransform(); +} diff --git a/webview/script.js b/webview/script.js index 1b843ea..12b0f3c 100644 --- a/webview/script.js +++ b/webview/script.js @@ -16,13 +16,14 @@ import './components/setupRenderers.js'; import { renderAll } from './core/renderAll.js'; window.addEventListener('message', (event) => { - const message = event.data; - if (message.type == 'launchmap-data') { - renderAll(message.data); - } + const message = event.data; + if (message.type == 'launchmap-data') { + renderAll(message.data); + } }); +// eslint-disable-next-line no-undef const vscode = acquireVsCodeApi(); document.getElementById('export-btn')?.addEventListener('click', () => { - vscode.postMessage({ type: 'export-json' }); -}); \ No newline at end of file + vscode.postMessage({ type: 'export-json' }); +}); diff --git a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/argument-drag-darwin.png b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/argument-drag-darwin.png deleted file mode 100644 index c373d5b..0000000 Binary files a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/argument-drag-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/argument-drag-linux.png b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/argument-drag-linux.png new file mode 100644 index 0000000..078c327 Binary files /dev/null and b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/argument-drag-linux.png differ diff --git a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/composable-container-drag-darwin.png b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/composable-container-drag-darwin.png deleted file mode 100644 index 332c9c3..0000000 Binary files a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/composable-container-drag-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/composable-container-drag-linux.png b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/composable-container-drag-linux.png new file mode 100644 index 0000000..f2d9685 Binary files /dev/null and b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/composable-container-drag-linux.png differ diff --git a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/composable-node-drag-darwin.png b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/composable-node-drag-darwin.png deleted file mode 100644 index 0acd71c..0000000 Binary files a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/composable-node-drag-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/composable-node-drag-linux.png b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/composable-node-drag-linux.png new file mode 100644 index 0000000..7b77d3c Binary files /dev/null and b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/composable-node-drag-linux.png differ diff --git a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/env-var-drag-darwin.png b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/env-var-drag-darwin.png deleted file mode 100644 index b0103ee..0000000 Binary files a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/env-var-drag-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/env-var-drag-linux.png b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/env-var-drag-linux.png new file mode 100644 index 0000000..b9ac7cc Binary files /dev/null and b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/env-var-drag-linux.png differ diff --git a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/event-handler-drag-darwin.png b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/event-handler-drag-darwin.png deleted file mode 100644 index debe2c8..0000000 Binary files a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/event-handler-drag-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/event-handler-drag-linux.png b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/event-handler-drag-linux.png new file mode 100644 index 0000000..6073630 Binary files /dev/null and b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/event-handler-drag-linux.png differ diff --git a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/group-drag-darwin.png b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/group-drag-darwin.png deleted file mode 100644 index 2323d27..0000000 Binary files a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/group-drag-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/group-drag-linux.png b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/group-drag-linux.png new file mode 100644 index 0000000..1bcca84 Binary files /dev/null and b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/group-drag-linux.png differ diff --git a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/group-node-drag-darwin.png b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/group-node-drag-darwin.png deleted file mode 100644 index 0b7e42b..0000000 Binary files a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/group-node-drag-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/group-node-drag-linux.png b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/group-node-drag-linux.png new file mode 100644 index 0000000..7e3e6c6 Binary files /dev/null and b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/group-node-drag-linux.png differ diff --git a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/include-drag-darwin.png b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/include-drag-darwin.png deleted file mode 100644 index ae8e834..0000000 Binary files a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/include-drag-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/include-drag-linux.png b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/include-drag-linux.png new file mode 100644 index 0000000..3c348b2 Binary files /dev/null and b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/include-drag-linux.png differ diff --git a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/node-drag-darwin.png b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/node-drag-darwin.png deleted file mode 100644 index 8eb52ef..0000000 Binary files a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/node-drag-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/node-drag-linux.png b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/node-drag-linux.png new file mode 100644 index 0000000..ff44029 Binary files /dev/null and b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/node-drag-linux.png differ diff --git a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/opaque-drag-darwin.png b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/opaque-drag-darwin.png deleted file mode 100644 index 936b0c5..0000000 Binary files a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/opaque-drag-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/opaque-drag-linux.png b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/opaque-drag-linux.png new file mode 100644 index 0000000..a4d1c67 Binary files /dev/null and b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/opaque-drag-linux.png differ diff --git a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/opaque-node-drag-darwin.png b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/opaque-node-drag-darwin.png deleted file mode 100644 index 696938c..0000000 Binary files a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/opaque-node-drag-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/opaque-node-drag-linux.png b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/opaque-node-drag-linux.png new file mode 100644 index 0000000..96d7961 Binary files /dev/null and b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/opaque-node-drag-linux.png differ diff --git a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/python-expression-drag-darwin.png b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/python-expression-drag-darwin.png deleted file mode 100644 index dd69e0b..0000000 Binary files a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/python-expression-drag-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/python-expression-drag-linux.png b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/python-expression-drag-linux.png new file mode 100644 index 0000000..959b220 Binary files /dev/null and b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/python-expression-drag-linux.png differ diff --git a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/timer-action-drag-darwin.png b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/timer-action-drag-darwin.png deleted file mode 100644 index 3671b85..0000000 Binary files a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/timer-action-drag-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/timer-action-drag-linux.png b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/timer-action-drag-linux.png new file mode 100644 index 0000000..2709190 Binary files /dev/null and b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/timer-action-drag-linux.png differ diff --git a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/timer-action-node-drag-darwin.png b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/timer-action-node-drag-darwin.png deleted file mode 100644 index 8609ba5..0000000 Binary files a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/timer-action-node-drag-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/component-drag.spec.js-snapshots/timer-action-node-drag-linux.png b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/timer-action-node-drag-linux.png new file mode 100644 index 0000000..33b3a2c Binary files /dev/null and b/webview/tests/__screenshots__/component-drag.spec.js-snapshots/timer-action-node-drag-linux.png differ diff --git a/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-1-py-json-final-darwin.png b/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-1-py-json-final-darwin.png deleted file mode 100644 index d62e065..0000000 Binary files a/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-1-py-json-final-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-1-py-json-final-linux.png b/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-1-py-json-final-linux.png new file mode 100644 index 0000000..ebe59fc Binary files /dev/null and b/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-1-py-json-final-linux.png differ diff --git a/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-2-py-json-final-darwin.png b/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-2-py-json-final-darwin.png deleted file mode 100644 index 8c80a05..0000000 Binary files a/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-2-py-json-final-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-2-py-json-final-linux.png b/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-2-py-json-final-linux.png new file mode 100644 index 0000000..365cbeb Binary files /dev/null and b/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-2-py-json-final-linux.png differ diff --git a/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-3-py-json-final-darwin.png b/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-3-py-json-final-darwin.png deleted file mode 100644 index de0550c..0000000 Binary files a/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-3-py-json-final-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-3-py-json-final-linux.png b/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-3-py-json-final-linux.png new file mode 100644 index 0000000..c260e82 Binary files /dev/null and b/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-3-py-json-final-linux.png differ diff --git a/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-4-py-json-final-darwin.png b/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-4-py-json-final-darwin.png deleted file mode 100644 index f1d1ae9..0000000 Binary files a/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-4-py-json-final-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-4-py-json-final-linux.png b/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-4-py-json-final-linux.png new file mode 100644 index 0000000..55c9065 Binary files /dev/null and b/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-4-py-json-final-linux.png differ diff --git a/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-5-py-json-final-darwin.png b/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-5-py-json-final-darwin.png deleted file mode 100644 index b22e924..0000000 Binary files a/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-5-py-json-final-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-5-py-json-final-linux.png b/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-5-py-json-final-linux.png new file mode 100644 index 0000000..ae00d94 Binary files /dev/null and b/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-5-py-json-final-linux.png differ diff --git a/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-6-py-json-final-darwin.png b/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-6-py-json-final-darwin.png deleted file mode 100644 index 216930c..0000000 Binary files a/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-6-py-json-final-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-6-py-json-final-linux.png b/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-6-py-json-final-linux.png new file mode 100644 index 0000000..b805a0b Binary files /dev/null and b/webview/tests/__screenshots__/interaction.spec.js-snapshots/launch-file-6-py-json-final-linux.png differ diff --git a/webview/tests/__screenshots__/interaction.spec.js-snapshots/nav2-bringup-launch-py-json-final-darwin.png b/webview/tests/__screenshots__/interaction.spec.js-snapshots/nav2-bringup-launch-py-json-final-darwin.png deleted file mode 100644 index 1128271..0000000 Binary files a/webview/tests/__screenshots__/interaction.spec.js-snapshots/nav2-bringup-launch-py-json-final-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/interaction.spec.js-snapshots/nav2-bringup-launch-py-json-final-linux.png b/webview/tests/__screenshots__/interaction.spec.js-snapshots/nav2-bringup-launch-py-json-final-linux.png new file mode 100644 index 0000000..adf18d8 Binary files /dev/null and b/webview/tests/__screenshots__/interaction.spec.js-snapshots/nav2-bringup-launch-py-json-final-linux.png differ diff --git a/webview/tests/__screenshots__/interaction.spec.js-snapshots/rrbot-launch-py-json-final-darwin.png b/webview/tests/__screenshots__/interaction.spec.js-snapshots/rrbot-launch-py-json-final-darwin.png deleted file mode 100644 index 4bf95bf..0000000 Binary files a/webview/tests/__screenshots__/interaction.spec.js-snapshots/rrbot-launch-py-json-final-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/interaction.spec.js-snapshots/rrbot-launch-py-json-final-linux.png b/webview/tests/__screenshots__/interaction.spec.js-snapshots/rrbot-launch-py-json-final-linux.png new file mode 100644 index 0000000..96f5540 Binary files /dev/null and b/webview/tests/__screenshots__/interaction.spec.js-snapshots/rrbot-launch-py-json-final-linux.png differ diff --git a/webview/tests/__screenshots__/interaction.spec.js-snapshots/turtlebot3-bringup-launch-py-json-final-darwin.png b/webview/tests/__screenshots__/interaction.spec.js-snapshots/turtlebot3-bringup-launch-py-json-final-darwin.png deleted file mode 100644 index d90a8fe..0000000 Binary files a/webview/tests/__screenshots__/interaction.spec.js-snapshots/turtlebot3-bringup-launch-py-json-final-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/interaction.spec.js-snapshots/turtlebot3-bringup-launch-py-json-final-linux.png b/webview/tests/__screenshots__/interaction.spec.js-snapshots/turtlebot3-bringup-launch-py-json-final-linux.png new file mode 100644 index 0000000..61dd2da Binary files /dev/null and b/webview/tests/__screenshots__/interaction.spec.js-snapshots/turtlebot3-bringup-launch-py-json-final-linux.png differ diff --git a/webview/tests/__screenshots__/interaction.spec.js-snapshots/turtlebot4-bringup-launch-py-json-final-darwin.png b/webview/tests/__screenshots__/interaction.spec.js-snapshots/turtlebot4-bringup-launch-py-json-final-darwin.png deleted file mode 100644 index b224f9c..0000000 Binary files a/webview/tests/__screenshots__/interaction.spec.js-snapshots/turtlebot4-bringup-launch-py-json-final-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/interaction.spec.js-snapshots/turtlebot4-bringup-launch-py-json-final-linux.png b/webview/tests/__screenshots__/interaction.spec.js-snapshots/turtlebot4-bringup-launch-py-json-final-linux.png new file mode 100644 index 0000000..b3fda47 Binary files /dev/null and b/webview/tests/__screenshots__/interaction.spec.js-snapshots/turtlebot4-bringup-launch-py-json-final-linux.png differ diff --git a/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-1-py-json-darwin.png b/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-1-py-json-darwin.png deleted file mode 100644 index c82e166..0000000 Binary files a/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-1-py-json-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-1-py-json-linux.png b/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-1-py-json-linux.png new file mode 100644 index 0000000..53486e4 Binary files /dev/null and b/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-1-py-json-linux.png differ diff --git a/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-2-py-json-darwin.png b/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-2-py-json-darwin.png deleted file mode 100644 index faca2c7..0000000 Binary files a/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-2-py-json-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-2-py-json-linux.png b/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-2-py-json-linux.png new file mode 100644 index 0000000..186fa57 Binary files /dev/null and b/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-2-py-json-linux.png differ diff --git a/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-3-py-json-darwin.png b/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-3-py-json-darwin.png deleted file mode 100644 index 514bd03..0000000 Binary files a/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-3-py-json-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-3-py-json-linux.png b/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-3-py-json-linux.png new file mode 100644 index 0000000..744e9fb Binary files /dev/null and b/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-3-py-json-linux.png differ diff --git a/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-4-py-json-darwin.png b/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-4-py-json-darwin.png deleted file mode 100644 index 2764974..0000000 Binary files a/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-4-py-json-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-4-py-json-linux.png b/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-4-py-json-linux.png new file mode 100644 index 0000000..89e3508 Binary files /dev/null and b/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-4-py-json-linux.png differ diff --git a/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-5-py-json-darwin.png b/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-5-py-json-darwin.png deleted file mode 100644 index 9265e17..0000000 Binary files a/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-5-py-json-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-5-py-json-linux.png b/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-5-py-json-linux.png new file mode 100644 index 0000000..b120def Binary files /dev/null and b/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-5-py-json-linux.png differ diff --git a/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-6-py-json-darwin.png b/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-6-py-json-darwin.png deleted file mode 100644 index 03f6f32..0000000 Binary files a/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-6-py-json-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-6-py-json-linux.png b/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-6-py-json-linux.png new file mode 100644 index 0000000..3834a80 Binary files /dev/null and b/webview/tests/__screenshots__/visual.spec.js-snapshots/launch-file-6-py-json-linux.png differ diff --git a/webview/tests/__screenshots__/visual.spec.js-snapshots/nav2-bringup-launch-py-json-darwin.png b/webview/tests/__screenshots__/visual.spec.js-snapshots/nav2-bringup-launch-py-json-darwin.png deleted file mode 100644 index abc192b..0000000 Binary files a/webview/tests/__screenshots__/visual.spec.js-snapshots/nav2-bringup-launch-py-json-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/visual.spec.js-snapshots/nav2-bringup-launch-py-json-linux.png b/webview/tests/__screenshots__/visual.spec.js-snapshots/nav2-bringup-launch-py-json-linux.png new file mode 100644 index 0000000..5f3cbc6 Binary files /dev/null and b/webview/tests/__screenshots__/visual.spec.js-snapshots/nav2-bringup-launch-py-json-linux.png differ diff --git a/webview/tests/__screenshots__/visual.spec.js-snapshots/rrbot-launch-py-json-darwin.png b/webview/tests/__screenshots__/visual.spec.js-snapshots/rrbot-launch-py-json-darwin.png deleted file mode 100644 index c8fd060..0000000 Binary files a/webview/tests/__screenshots__/visual.spec.js-snapshots/rrbot-launch-py-json-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/visual.spec.js-snapshots/rrbot-launch-py-json-linux.png b/webview/tests/__screenshots__/visual.spec.js-snapshots/rrbot-launch-py-json-linux.png new file mode 100644 index 0000000..9796358 Binary files /dev/null and b/webview/tests/__screenshots__/visual.spec.js-snapshots/rrbot-launch-py-json-linux.png differ diff --git a/webview/tests/__screenshots__/visual.spec.js-snapshots/turtlebot3-bringup-launch-py-json-darwin.png b/webview/tests/__screenshots__/visual.spec.js-snapshots/turtlebot3-bringup-launch-py-json-darwin.png deleted file mode 100644 index 0ceddba..0000000 Binary files a/webview/tests/__screenshots__/visual.spec.js-snapshots/turtlebot3-bringup-launch-py-json-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/visual.spec.js-snapshots/turtlebot3-bringup-launch-py-json-linux.png b/webview/tests/__screenshots__/visual.spec.js-snapshots/turtlebot3-bringup-launch-py-json-linux.png new file mode 100644 index 0000000..9b59cae Binary files /dev/null and b/webview/tests/__screenshots__/visual.spec.js-snapshots/turtlebot3-bringup-launch-py-json-linux.png differ diff --git a/webview/tests/__screenshots__/visual.spec.js-snapshots/turtlebot4-bringup-launch-py-json-darwin.png b/webview/tests/__screenshots__/visual.spec.js-snapshots/turtlebot4-bringup-launch-py-json-darwin.png deleted file mode 100644 index b281f8f..0000000 Binary files a/webview/tests/__screenshots__/visual.spec.js-snapshots/turtlebot4-bringup-launch-py-json-darwin.png and /dev/null differ diff --git a/webview/tests/__screenshots__/visual.spec.js-snapshots/turtlebot4-bringup-launch-py-json-linux.png b/webview/tests/__screenshots__/visual.spec.js-snapshots/turtlebot4-bringup-launch-py-json-linux.png new file mode 100644 index 0000000..896c285 Binary files /dev/null and b/webview/tests/__screenshots__/visual.spec.js-snapshots/turtlebot4-bringup-launch-py-json-linux.png differ diff --git a/webview/tests/fixtures/global-setup.js b/webview/tests/fixtures/global-setup.js index 9202b0f..a3ac4ae 100644 --- a/webview/tests/fixtures/global-setup.js +++ b/webview/tests/fixtures/global-setup.js @@ -16,15 +16,15 @@ import { TestServer } from './server.js'; let server; -async function globalSetup(config) { - server = new TestServer(); - await server.start(); +async function globalSetup() { + server = new TestServer(); + await server.start(); - process.env.TEST_SERVER_URL = server.url; + process.env.TEST_SERVER_URL = server.url; - return async () => { - await server.stop(); - }; + return async () => { + await server.stop(); + }; } -export default globalSetup; \ No newline at end of file +export default globalSetup; diff --git a/webview/tests/fixtures/sample-data.js b/webview/tests/fixtures/sample-data.js index b07c9d1..686cb4b 100644 --- a/webview/tests/fixtures/sample-data.js +++ b/webview/tests/fixtures/sample-data.js @@ -13,77 +13,77 @@ // limitations under the License. export const SAMPLE_DATA = { - arguments: [ - { name: "robot_name", default_value: "rrbot" } - ], - environment_variables: [ - { name: "ROS_DISTRO", default_value: "humble" } - ], - python_expressions: [ - { - body: "if ROS_DISTRO == 'humble': tb3_param_dir = LaunchConfiguration('tb3_param_dir')", - variables: ["${var:tb3_param_dir}"] - } - ], - nodes: [ - { package: "demo_nodes", executable: "talker", name: "talker_node", output: "screen" } - ], - includes: [ - { launch_file: "demo.launch.py", arguments: [] } - ], - groups: [ - { namespace: "test_ns", actions: { nodes: [{ package: "demo_pkg", executable: "listener" }]}} - ], - timer_actions: [ - { - period: "5.0", - actions: { - nodes: [{ package: "demo_nodes", executable: "delayed_talker" }] - } - } - ], - opaque_functions: [ - { - name: "launch_setup", - returns: { - nodes: [ - { package: "robot_state_publisher", executable: "rsp", name: "rsp_node" } - ] - } - } - ], - composable_nodes_container: [ - { - target_container: "my_container", - package: "rclcpp_components", - executable: "component_container_mt", - composable_nodes: [ - { package: "bar_pkg", plugin: "bar_pkg/BarNode", name: "bar" } - ] - } - ], - event_handlers: [ - { - type: "OnProcessExit", - triggered_by: ["nodes[0].events.triggers"], - triggers: ["nodes[0].events.triggered_by"] - } - ] -} + arguments: [ + { name: 'robot_name', default_value: 'rrbot' } + ], + environment_variables: [ + { name: 'ROS_DISTRO', default_value: 'humble' } + ], + python_expressions: [ + { + body: 'if ROS_DISTRO == \'humble\': tb3_param_dir = LaunchConfiguration(\'tb3_param_dir\')', + variables: ['${var:tb3_param_dir}'] + } + ], + nodes: [ + { package: 'demo_nodes', executable: 'talker', name: 'talker_node', output: 'screen' } + ], + includes: [ + { launch_file: 'demo.launch.py', arguments: [] } + ], + groups: [ + { namespace: 'test_ns', actions: { nodes: [{ package: 'demo_pkg', executable: 'listener' }] } } + ], + timer_actions: [ + { + period: '5.0', + actions: { + nodes: [{ package: 'demo_nodes', executable: 'delayed_talker' }] + } + } + ], + opaque_functions: [ + { + name: 'launch_setup', + returns: { + nodes: [ + { package: 'robot_state_publisher', executable: 'rsp', name: 'rsp_node' } + ] + } + } + ], + composable_nodes_container: [ + { + target_container: 'my_container', + package: 'rclcpp_components', + executable: 'component_container_mt', + composable_nodes: [ + { package: 'bar_pkg', plugin: 'bar_pkg/BarNode', name: 'bar' } + ] + } + ], + event_handlers: [ + { + type: 'OnProcessExit', + triggered_by: ['nodes[0].events.triggers'], + triggers: ['nodes[0].events.triggered_by'] + } + ] +}; export const COMPONENT_SELECTORS = [ - { type: "argument", selector: ".argument-block" }, - { type: "env-var", selector: ".environment-variable-block" }, - { type: "node", selector: ".node-block" }, - { type: "include", selector: ".include-block" }, - { type: "group", selector: ".group-header" }, - { type: "group-node", selector: ".group-block .node-block" }, - { type: "opaque", selector: ".opaque-function-header" }, - { type: "opaque-node", selector: ".opaque-function-block .node-block" }, - { type: "composable-container", selector: ".composable-container-header" }, - { type: "composable-node", selector: ".composable-node-block" }, - { type: "python-expression", selector: ".python-expression-block" }, - { type: "timer-action", selector: ".timer-action-header" }, - { type: "timer-action-node", selector: ".timer-action-block .node-block" }, - { type: "event-handler", selector: ".event-handler-block" } -]; \ No newline at end of file + { type: 'argument', selector: '.argument-block' }, + { type: 'env-var', selector: '.environment-variable-block' }, + { type: 'node', selector: '.node-block' }, + { type: 'include', selector: '.include-block' }, + { type: 'group', selector: '.group-header' }, + { type: 'group-node', selector: '.group-block .node-block' }, + { type: 'opaque', selector: '.opaque-function-header' }, + { type: 'opaque-node', selector: '.opaque-function-block .node-block' }, + { type: 'composable-container', selector: '.composable-container-header' }, + { type: 'composable-node', selector: '.composable-node-block' }, + { type: 'python-expression', selector: '.python-expression-block' }, + { type: 'timer-action', selector: '.timer-action-header' }, + { type: 'timer-action-node', selector: '.timer-action-block .node-block' }, + { type: 'event-handler', selector: '.event-handler-block' } +]; diff --git a/webview/tests/fixtures/server.js b/webview/tests/fixtures/server.js index 71dc033..b4c19db 100644 --- a/webview/tests/fixtures/server.js +++ b/webview/tests/fixtures/server.js @@ -17,27 +17,27 @@ import http from 'http'; import serveHandler from 'serve-handler'; export class TestServer { - constructor(port = 3000, publicDir = path.join(__dirname, '../../../webview')) { - this.port = port; - this.publicDir = publicDir; - this.server = null; - } + constructor(port = 3000, publicDir = path.join(__dirname, '../../../webview')) { + this.port = port; + this.publicDir = publicDir; + this.server = null; + } - async start() { - this.server = http.createServer((req, res) => - serveHandler(req, res, { public: this.publicDir }) - ); - await new Promise((resolve) => this.server.listen(this.port, resolve)); - console.log(`โœ… Test server running at http://localhost:${this.port}`); - } + async start() { + this.server = http.createServer((req, res) => + serveHandler(req, res, { public: this.publicDir }) + ); + await new Promise((resolve) => this.server.listen(this.port, resolve)); + console.log(`โœ… Test server running at http://localhost:${this.port}`); + } - async stop() { - if (!this.server) return; - await new Promise((resolve) => this.server.close(resolve)); - console.log(`๐Ÿ›‘ Test server stopped`); - } + async stop() { + if (!this.server) return; + await new Promise((resolve) => this.server.close(resolve)); + console.log('๐Ÿ›‘ Test server stopped'); + } - get url() { - return `http://localhost:${this.port}/tests/assets/index.html`; - } -} \ No newline at end of file + get url() { + return `http://localhost:${this.port}/tests/assets/index.html`; + } +} diff --git a/webview/tests/playwright.config.js b/webview/tests/playwright.config.js index 4541a98..fedfdad 100644 --- a/webview/tests/playwright.config.js +++ b/webview/tests/playwright.config.js @@ -15,13 +15,13 @@ import { defineConfig } from '@playwright/test'; export default defineConfig({ - testDir: './specs', - snapshotDir: './__screenshots__', - globalSetup: './fixtures/global-setup.js', - use: { - headless: true, - viewport: { width: 1600, height: 900 }, - baseURL: process.env.TEST_SERVER_URL || 'http://localhost:3000', - }, - reporter: [['list'], ['html']], -}); \ No newline at end of file + testDir: './specs', + snapshotDir: './__screenshots__', + globalSetup: './fixtures/global-setup.js', + use: { + headless: true, + viewport: { width: 1600, height: 900 }, + baseURL: process.env.TEST_SERVER_URL || 'http://localhost:3000', + }, + reporter: [['list'], ['html']], +}); diff --git a/webview/tests/specs/component-drag.spec.js b/webview/tests/specs/component-drag.spec.js index ebcdce7..126704e 100644 --- a/webview/tests/specs/component-drag.spec.js +++ b/webview/tests/specs/component-drag.spec.js @@ -15,30 +15,30 @@ import { test, expect } from '@playwright/test'; import { SAMPLE_DATA, COMPONENT_SELECTORS } from '../fixtures/sample-data'; -test.describe("Component Drag Tests", () => { - COMPONENT_SELECTORS.forEach(({ type, selector }) => { - test(`dragging ${type} works`, async ({ page }) => { - await page.goto(process.env.TEST_SERVER_URL); +test.describe('Component Drag Tests', () => { + COMPONENT_SELECTORS.forEach(({ type, selector }) => { + test(`dragging ${type} works`, async ({ page }) => { + await page.goto(process.env.TEST_SERVER_URL); - await page.evaluate((data) => { - window.postMessage({ type: 'launchmap-data', data }, '*'); - }, SAMPLE_DATA); + await page.evaluate((data) => { + window.postMessage({ type: 'launchmap-data', data }, '*'); + }, SAMPLE_DATA); - const block = page.locator(selector).first(); - const boxBefore = await block.boundingBox(); + const block = page.locator(selector).first(); + const boxBefore = await block.boundingBox(); - await block.dragTo(page.locator('body'), { - targetPosition: { x: 300, y: 200 }, - }); + await block.dragTo(page.locator('body'), { + targetPosition: { x: 300, y: 200 }, + }); - const boxAfter = await block.boundingBox(); - expect(boxAfter.x).not.toBe(boxBefore.x); - expect(boxAfter.y).not.toBe(boxBefore.y); + const boxAfter = await block.boundingBox(); + expect(boxAfter.x).not.toBe(boxBefore.x); + expect(boxAfter.y).not.toBe(boxBefore.y); - await expect(page).toHaveScreenshot(`${type}-drag.png`, { - fullPage: true, - maxDiffPixelRatio: 0.02 - }); - }); + await expect(page).toHaveScreenshot(`${type}-drag.png`, { + fullPage: true, + maxDiffPixelRatio: 0.02 + }); }); -}); \ No newline at end of file + }); +}); diff --git a/webview/tests/specs/interaction.spec.js b/webview/tests/specs/interaction.spec.js index f5a456b..d8f2e01 100644 --- a/webview/tests/specs/interaction.spec.js +++ b/webview/tests/specs/interaction.spec.js @@ -18,94 +18,94 @@ import path from 'path'; const launchFilesDir = path.resolve(__dirname, '../../../parser/tests/real_cases/expected_outputs'); -test.describe("Zoom, Pan, and Drag interactions with visualization state checks", () => { - const files = fs.readdirSync(launchFilesDir).filter(f => f.endsWith(".json")); - - for (const file of files) { - test(`state consistent for ${file}`, async ({ page }) => { - await page.goto(process.env.TEST_SERVER_URL); - - const launchData = JSON.parse( - fs.readFileSync(path.join(launchFilesDir, file), "utf8") - ); - - await page.evaluate((data) => { - window.postMessage({ type: 'launchmap-data', data }, '*'); - }, launchData); - - await page.waitForSelector(".block"); - - // Capture initial edge positions - const edgesBefore = await page.$$eval('#edge-layer path', paths => - paths.map(p => p.getAttribute('d')) - ); - if (edgesBefore.length === 0) { - console.warn(`โš ๏ธ No edges found in ${file}, skipping edge checks.`); - } - - // Drag Test - const block = page.locator('.block-header').first(); - const boxBefore = await block.boundingBox(); - - await block.dragTo(page.locator('body'), { - targetPosition: { x: 300, y: 200 }, - }); - - const boxAfter = await block.boundingBox(); - expect(boxAfter.x).not.toBe(boxBefore.x); - expect(boxAfter.y).not.toBe(boxBefore.y); - - const edgesAfterDrag = await page.$$eval('#edge-layer path', paths => - paths.map(p => p.getAttribute('d')) - ); - if (edgesBefore.length > 0) { - expect(edgesAfterDrag).not.toEqual(edgesBefore); - } - - // Pan test - const zoomLayer = page.locator('#zoom-layer'); - const transformBeforePan = await zoomLayer.evaluate(el => el.style.transform); - - await page.keyboard.down('Shift'); - await page.mouse.move(300, 300); - await page.mouse.down(); - await page.mouse.move(500, 400); - await page.mouse.up(); - await page.keyboard.up('Shift'); - - const transformAfterPan = await zoomLayer.evaluate(el => el.style.transform); - expect(transformAfterPan).not.toBe(transformBeforePan); - - const edgesAfterPan = await page.$$eval('#edge-layer path', paths => - paths.map(p => p.getAttribute('d')) - ); - if (edgesBefore.length > 0) { - expect(edgesAfterPan).not.toEqual(edgesAfterDrag); - } - - // Zoom Test - const transformBeforeZoom = await zoomLayer.evaluate(el => el.style.transform); - - await page.mouse.wheel(0, -200); - const transformAfterZoomIn = await zoomLayer.evaluate(el => el.style.transform); - expect(transformAfterZoomIn).not.toBe(transformBeforeZoom); - - await page.mouse.wheel(0, 200); - const transformAfterZoomOut = await zoomLayer.evaluate(el => el.style.transform); - expect(transformAfterZoomOut).not.toBe(transformAfterZoomIn); - - const edgesAfterZoom = await page.$$eval('#edge-layer path', paths => - paths.map(p => p.getAttribute('d')) - ); - if (edgesBefore.length > 0) { - expect(edgesAfterZoom).not.toEqual(edgesAfterPan); - } - - // Final Visual Snapshot - await expect(page).toHaveScreenshot(`${file}-final.png`, { - fullPage: true, - maxDiffPixelRatio: 0.02 - }); - }); - } -}); \ No newline at end of file +test.describe('Zoom, Pan, and Drag interactions with visualization state checks', () => { + const files = fs.readdirSync(launchFilesDir).filter(f => f.endsWith('.json')); + + for (const file of files) { + test(`state consistent for ${file}`, async ({ page }) => { + await page.goto(process.env.TEST_SERVER_URL); + + const launchData = JSON.parse( + fs.readFileSync(path.join(launchFilesDir, file), 'utf8') + ); + + await page.evaluate((data) => { + window.postMessage({ type: 'launchmap-data', data }, '*'); + }, launchData); + + await page.waitForSelector('.block'); + + // Capture initial edge positions + const edgesBefore = await page.$$eval('#edge-layer path', paths => + paths.map(p => p.getAttribute('d')) + ); + if (edgesBefore.length === 0) { + console.warn(`โš ๏ธ No edges found in ${file}, skipping edge checks.`); + } + + // Drag Test + const block = page.locator('.block-header').first(); + const boxBefore = await block.boundingBox(); + + await block.dragTo(page.locator('body'), { + targetPosition: { x: 300, y: 200 }, + }); + + const boxAfter = await block.boundingBox(); + expect(boxAfter.x).not.toBe(boxBefore.x); + expect(boxAfter.y).not.toBe(boxBefore.y); + + const edgesAfterDrag = await page.$$eval('#edge-layer path', paths => + paths.map(p => p.getAttribute('d')) + ); + if (edgesBefore.length > 0) { + expect(edgesAfterDrag).not.toEqual(edgesBefore); + } + + // Pan test + const zoomLayer = page.locator('#zoom-layer'); + const transformBeforePan = await zoomLayer.evaluate(el => el.style.transform); + + await page.keyboard.down('Shift'); + await page.mouse.move(300, 300); + await page.mouse.down(); + await page.mouse.move(500, 400); + await page.mouse.up(); + await page.keyboard.up('Shift'); + + const transformAfterPan = await zoomLayer.evaluate(el => el.style.transform); + expect(transformAfterPan).not.toBe(transformBeforePan); + + const edgesAfterPan = await page.$$eval('#edge-layer path', paths => + paths.map(p => p.getAttribute('d')) + ); + if (edgesBefore.length > 0) { + expect(edgesAfterPan).not.toEqual(edgesAfterDrag); + } + + // Zoom Test + const transformBeforeZoom = await zoomLayer.evaluate(el => el.style.transform); + + await page.mouse.wheel(0, -200); + const transformAfterZoomIn = await zoomLayer.evaluate(el => el.style.transform); + expect(transformAfterZoomIn).not.toBe(transformBeforeZoom); + + await page.mouse.wheel(0, 200); + const transformAfterZoomOut = await zoomLayer.evaluate(el => el.style.transform); + expect(transformAfterZoomOut).not.toBe(transformAfterZoomIn); + + const edgesAfterZoom = await page.$$eval('#edge-layer path', paths => + paths.map(p => p.getAttribute('d')) + ); + if (edgesBefore.length > 0) { + expect(edgesAfterZoom).not.toEqual(edgesAfterPan); + } + + // Final Visual Snapshot + await expect(page).toHaveScreenshot(`${file}-final.png`, { + fullPage: true, + maxDiffPixelRatio: 0.02 + }); + }); + } +}); diff --git a/webview/tests/specs/visual.spec.js b/webview/tests/specs/visual.spec.js index 22a7e8d..8d7243d 100644 --- a/webview/tests/specs/visual.spec.js +++ b/webview/tests/specs/visual.spec.js @@ -18,26 +18,26 @@ import path from 'path'; const launchFilesDir = path.resolve(__dirname, '../../../parser/tests/real_cases/expected_outputs'); -test.describe("LaunchMap Visual Tests", () => { - const files = fs.readdirSync(launchFilesDir).filter(f => f.endsWith(".json")); +test.describe('LaunchMap Visual Tests', () => { + const files = fs.readdirSync(launchFilesDir).filter(f => f.endsWith('.json')); - for (const file of files) { - test(`renders correctly for ${file}`, async ({ page }) => { - await page.goto(process.env.TEST_SERVER_URL); + for (const file of files) { + test(`renders correctly for ${file}`, async ({ page }) => { + await page.goto(process.env.TEST_SERVER_URL); - const launchData = JSON.parse( - fs.readFileSync(path.join(launchFilesDir, file), "utf8") - ); + const launchData = JSON.parse( + fs.readFileSync(path.join(launchFilesDir, file), 'utf8') + ); - await page.evaluate((data) => { - window.postMessage({ type: 'launchmap-data', data }, '*'); - }, launchData); + await page.evaluate((data) => { + window.postMessage({ type: 'launchmap-data', data }, '*'); + }, launchData); - await page.waitForSelector(".block"); - await expect(page).toHaveScreenshot(`${file}.png`, { - fullPage: true, - maxDiffPixelRatio: 0.01 - }); - }); - } -}); \ No newline at end of file + await page.waitForSelector('.block'); + await expect(page).toHaveScreenshot(`${file}.png`, { + fullPage: true, + maxDiffPixelRatio: 0.01 + }); + }); + } +}); diff --git a/webview/utils/autoFitToScreen.js b/webview/utils/autoFitToScreen.js index 7df8fb3..662bb65 100644 --- a/webview/utils/autoFitToScreen.js +++ b/webview/utils/autoFitToScreen.js @@ -13,49 +13,49 @@ // limitations under the License. export function autoFitToScreen(editor, zoomLayer, margin = 40) { - if (!window.zoomState) return; - const zoomState = window.zoomState; + if (!window.zoomState) return; + const zoomState = window.zoomState; - const prevTransform = zoomLayer.style.transform; - zoomLayer.style.transform = "none"; + const prevTransform = zoomLayer.style.transform; + zoomLayer.style.transform = 'none'; - const children = zoomLayer.querySelectorAll(".block"); - if (!children.length) { - zoomLayer.style.transform = prevTransform; - return; - } + const children = zoomLayer.querySelectorAll('.block'); + if (!children.length) { + zoomLayer.style.transform = prevTransform; + return; + } - let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; - children.forEach(child => { - const rect = child.getBoundingClientRect(); - const editorRect = editor.getBoundingClientRect(); + let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; + children.forEach(child => { + const rect = child.getBoundingClientRect(); + const editorRect = editor.getBoundingClientRect(); - const relativeLeft = rect.left - editorRect.left; - const relativeTop = rect.top - editorRect.top; + const relativeLeft = rect.left - editorRect.left; + const relativeTop = rect.top - editorRect.top; - minX = Math.min(minX, relativeLeft); - minY = Math.min(minY, relativeTop); - maxX = Math.max(maxX, relativeLeft + rect.width); - maxY = Math.max(maxY, relativeTop + rect.height); - }); + minX = Math.min(minX, relativeLeft); + minY = Math.min(minY, relativeTop); + maxX = Math.max(maxX, relativeLeft + rect.width); + maxY = Math.max(maxY, relativeTop + rect.height); + }); - const contentWidth = maxX - minX; - const contentHeight = maxY - minY; + const contentWidth = maxX - minX; + const contentHeight = maxY - minY; - const editorRect = editor.getBoundingClientRect(); - const availableWidth = editorRect.width - margin * 2; - const availableHeight = editorRect.height - margin * 2; + const editorRect = editor.getBoundingClientRect(); + const availableWidth = editorRect.width - margin * 2; + const availableHeight = editorRect.height - margin * 2; - let scale = Math.min(availableWidth / contentWidth, availableHeight / contentHeight); - scale = Math.max(0.2, Math.min(3, scale)); + let scale = Math.min(availableWidth / contentWidth, availableHeight / contentHeight); + scale = Math.max(0.2, Math.min(3, scale)); - zoomState.scale = scale; - zoomState.offsetX = -minX * scale + (availableWidth - contentWidth * scale) / 2 + margin; - zoomState.offsetY = -minY * scale + (availableHeight - contentHeight * scale) / 2 + margin; + zoomState.scale = scale; + zoomState.offsetX = -minX * scale + (availableWidth - contentWidth * scale) / 2 + margin; + zoomState.offsetY = -minY * scale + (availableHeight - contentHeight * scale) / 2 + margin; - zoomLayer.style.transform = prevTransform; - const applyTransform = () => { - zoomLayer.style.transform = `translate(${zoomState.offsetX}px, ${zoomState.offsetY}px) scale(${zoomState.scale})`; - } - applyTransform(); -} \ No newline at end of file + zoomLayer.style.transform = prevTransform; + const applyTransform = () => { + zoomLayer.style.transform = `translate(${zoomState.offsetX}px, ${zoomState.offsetY}px) scale(${zoomState.scale})`; + }; + applyTransform(); +} diff --git a/webview/utils/drag.js b/webview/utils/drag.js index 1661937..3b16444 100644 --- a/webview/utils/drag.js +++ b/webview/utils/drag.js @@ -13,64 +13,64 @@ // limitations under the License. export function makeDraggable(element, options={}) { - let offsetX = 0, offsetY = 0, startX = 0, startY = 0; + let offsetX = 0, offsetY = 0, startX = 0, startY = 0; - // Ensure element has initial position - if (!element.style.left) element.style.left = "0px"; - if (!element.style.top) element.style.top = "0px"; + // Ensure element has initial position + if (!element.style.left) element.style.left = '0px'; + if (!element.style.top) element.style.top = '0px'; - element.onmousedown = dragMouseDown; + element.onmousedown = dragMouseDown; - function dragMouseDown(e) { - if (options.stopPropagation) e.stopPropagation(); - e.preventDefault(); - startX = e.clientX; - startY = e.clientY; + function dragMouseDown(e) { + if (options.stopPropagation) e.stopPropagation(); + e.preventDefault(); + startX = e.clientX; + startY = e.clientY; - document.onmousemove = elementDrag; - document.onmouseup = stopDrag; - } + document.onmousemove = elementDrag; + document.onmouseup = stopDrag; + } - function elementDrag(e) { - e.preventDefault(); + function elementDrag(e) { + e.preventDefault(); - const zoomScale = window.zoomState?.scale || 1; + const zoomScale = window.zoomState?.scale || 1; - offsetX = (e.clientX - startX) / zoomScale; - offsetY = (e.clientY - startY) / zoomScale; - startX = e.clientX; - startY = e.clientY; + offsetX = (e.clientX - startX) / zoomScale; + offsetY = (e.clientY - startY) / zoomScale; + startX = e.clientX; + startY = e.clientY; - const currentLeft = parseFloat(element.style.left || "0"); - const currentTop = parseFloat(element.style.top || "0"); + const currentLeft = parseFloat(element.style.left || '0'); + const currentTop = parseFloat(element.style.top || '0'); - let newLeft = currentLeft + offsetX; - let newTop = currentTop + offsetY; + let newLeft = currentLeft + offsetX; + let newTop = currentTop + offsetY; - if (options.constrainToParent) { - const parent = element.parentElement; - const parentWidth = parent.clientWidth; - const parentHeight = parent.clientHeight; - const elementWidth = element.offsetWidth; - const elementHeight = element.offsetHeight; + if (options.constrainToParent) { + const parent = element.parentElement; + const parentWidth = parent.clientWidth; + const parentHeight = parent.clientHeight; + const elementWidth = element.offsetWidth; + const elementHeight = element.offsetHeight; - const maxLeft = parentWidth - elementWidth; - const maxTop = parentHeight - elementHeight; + const maxLeft = parentWidth - elementWidth; + const maxTop = parentHeight - elementHeight; - newLeft = Math.max(0, Math.min(newLeft, maxLeft)); - newTop = Math.max(0, Math.min(newTop, maxTop)); - } + newLeft = Math.max(0, Math.min(newLeft, maxLeft)); + newTop = Math.max(0, Math.min(newTop, maxTop)); + } - element.style.left = `${newLeft}px`; - element.style.top = `${newTop}px`; + element.style.left = `${newLeft}px`; + element.style.top = `${newTop}px`; - if (typeof options.onDrag === "function") { - requestAnimationFrame(() => options.onDrag()); - } + if (typeof options.onDrag === 'function') { + requestAnimationFrame(() => options.onDrag()); } + } - function stopDrag() { - document.onmouseup = null; - document.onmousemove = null; - } -} \ No newline at end of file + function stopDrag() { + document.onmouseup = null; + document.onmousemove = null; + } +} diff --git a/webview/utils/labels.js b/webview/utils/labels.js index 320f297..9b048d2 100644 --- a/webview/utils/labels.js +++ b/webview/utils/labels.js @@ -13,18 +13,18 @@ // limitations under the License. export function getTypeLabel(type) { - switch (type) { - case 'node': return 'NODE'; - case 'include': return 'INCLUDE LAUNCH DESCRIPTION'; - case 'group': return 'GROUP ACTION'; - case 'timer-action': return 'TIMER ACTION'; - case 'argument': return 'DECLARE LAUNCH ARGUMENT'; - case 'environment-variable': return 'ENVIRONMENT VARIABLE'; - case 'opaque-function': return 'OPAQUE FUNCTION'; - case 'composable-node': return 'COMPOSABLE NODE'; - case 'composable-container': return 'COMPOSABLE NODE CONTAINER'; - case 'event-handler': return 'EVENT HANDLER'; - case 'python-expression': return 'PYTHON EXPRESSION'; - default: return `${type}`; - } -} \ No newline at end of file + switch (type) { + case 'node': return 'NODE'; + case 'include': return 'INCLUDE LAUNCH DESCRIPTION'; + case 'group': return 'GROUP ACTION'; + case 'timer-action': return 'TIMER ACTION'; + case 'argument': return 'DECLARE LAUNCH ARGUMENT'; + case 'environment-variable': return 'ENVIRONMENT VARIABLE'; + case 'opaque-function': return 'OPAQUE FUNCTION'; + case 'composable-node': return 'COMPOSABLE NODE'; + case 'composable-container': return 'COMPOSABLE NODE CONTAINER'; + case 'event-handler': return 'EVENT HANDLER'; + case 'python-expression': return 'PYTHON EXPRESSION'; + default: return `${type}`; + } +}