From d80aacf933df700dbc6c8ea3364607e62533b369 Mon Sep 17 00:00:00 2001 From: Douglas Gomes Date: Fri, 17 Apr 2026 16:31:39 -0300 Subject: [PATCH 01/17] chore: update Node.js version and add CLAUDE.md documentation - Removed .nvmrc file and added .tool-versions to specify Node.js version 24.12.0. - Introduced CLAUDE.md for guidance on using the Codebot extension, including commands, architecture, and testing instructions. - Updated package.json to reflect the new Node.js engine requirement. --- .nvmrc | 1 - .tool-versions | 1 + CLAUDE.md | 73 +++++++++ package-lock.json | 378 ++-------------------------------------------- package.json | 5 +- 5 files changed, 93 insertions(+), 365 deletions(-) delete mode 100644 .nvmrc create mode 100644 .tool-versions create mode 100644 CLAUDE.md diff --git a/.nvmrc b/.nvmrc deleted file mode 100644 index 832d385..0000000 --- a/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -16.14.0 diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..ef93fec --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +nodejs 24.12.0 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..8661c5c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,73 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Commands + +```bash +npm run compile # Webpack build (development) +npm run watch # Webpack watch mode +npm run package # Production build with hidden source maps +npm run build # Package extension as .vsix +npm run lint # ESLint + Prettier with auto-fix +npm run lint:check # Lint check without fixing (used in CI) +npm test # Run Jest tests +npm run test:watch # Jest watch mode +npm run test:coverage # Generate coverage reports +npm run dev-install # Compile, package, and install extension locally +``` + +To run a single test file: +```bash +npx jest src/test/path/to/file.test.ts +``` + +## Architecture + +Codebot is a VSCode extension that generates boilerplate code from Handlebars templates. Users right-click a folder in the Explorer, pick a template, enter a component name, and files are generated with `{{name}}` replaced by the component name (PascalCase). + +### Entry point + +`src/extension.ts` — registers two VSCode commands: `codebot.createComponent` and `codebot.updateComponent`. + +### Core flow + +1. **Command** (`src/commands/`) receives the folder URI from VSCode context +2. **ProjectDetector** (`src/managers/`) determines the project context (single vs. multi-workspace) +3. **ConfigurationManager** reads `codebot.config.json` for custom template paths +4. **TemplateManager** discovers `.hbs` template files and presents choices to the user +5. **Helpers** (`src/helpers/`) wrap VSCode UI (QuickPick, InputBox, file creation) +6. Template files are compiled via Handlebars and written to disk + +### Key modules + +- `src/managers/` — Core business logic. `TemplateManager` uses TTL-based caching and supports hierarchical template discovery. Managers accept dependencies via constructor injection. +- `src/commands/` — Thin handlers that orchestrate managers; `createComponent` generates new files, `updateComponent` adds missing template files to an existing component. +- `src/utils/` — `PathResolver` sanitizes and validates paths (prevents traversal attacks). `DirectoryProcessor` scans workspace directories. +- `src/errors/` — `CodebotError` typed error class with error category enum. +- `src/interfaces/` — TypeScript contracts for all managers and core data types (`ITemplateManager`, `IProjectDetector`, etc.). + +### Template structure (user-created) + +``` +project-root/ +├── templates/ +│ └── ComponentName/ +│ ├── ComponentName.tsx.hbs +│ └── index.tsx.hbs +└── codebot.config.json # optional, sets custom templatesFolderPath +``` + +Filenames containing the template folder name (or the literal string "Template") are renamed to the component name at generation time. + +## Build & packaging + +Webpack bundles everything into a single `dist/extension.js`. The `vscode` module is marked as external (provided by the host). TypeScript targets ES6 with strict mode enabled. + +## Testing + +Jest with `ts-jest`. The `vscode` module is mocked globally (see `src/test/__mocks__/vscode.ts`). Tests live alongside source in `src/test/`. + +## Release + +Semantic Release runs on pushes to `main` and publishes to the VS Marketplace. Commit messages must follow Conventional Commits (`feat:`, `fix:`, `chore:`, etc.) to trigger correct version bumps. diff --git a/package-lock.json b/package-lock.json index a683868..a1d117a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,6 +38,7 @@ "webpack-cli": "^5.0.1" }, "engines": { + "node": ">=24.12.0", "vscode": "^1.74.0" } }, @@ -86,6 +87,7 @@ "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -1327,6 +1329,7 @@ "integrity": "sha512-Czz/59VefU+kKDy+ZfDwtOIYIkFjExOKf+HA92aiTZJ6EfWpFzYQWw0l54ji8bVmyhc+mGaLUbSUmXazG7z5OQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^3.0.0", "@octokit/graphql": "^5.0.0", @@ -1971,6 +1974,7 @@ "integrity": "sha512-1mxNA8qfgxX8kBvRDIHEzrRGrKHQfQlbW6iHyfHYS0Q4X1af+S6mkLNtgCOsGVl8+/LUPrqdHMssAemkrQ01qg==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.48.0", "@typescript-eslint/types": "5.48.0", @@ -2630,6 +2634,7 @@ "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2691,6 +2696,7 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -3103,6 +3109,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001737", "electron-to-chromium": "^1.5.211", @@ -4302,6 +4309,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint/eslintrc": "^1.4.1", "@humanwhocodes/config-array": "^0.11.8", @@ -4451,6 +4459,7 @@ "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "array-includes": "^3.1.4", "array.prototype.flat": "^1.2.5", @@ -6282,6 +6291,7 @@ "integrity": "sha512-yC3JvpP/ZcAZX5rYCtXO/g9k6VTCQz0VFE2v1FpxytWzUqfDtu0XL/pwnNvptzYItvGwomh1ehomRNMOyhCJKw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "30.1.1", "@jest/types": "30.0.5", @@ -7292,6 +7302,7 @@ "integrity": "sha512-jPueVhumq7idETHkb203WDD4fMA3yV9emQ5vLwop58lu8bTclMghBWcYAavlDqIEMaisADinV1TooIFCfqOsYQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -8061,8 +8072,6 @@ }, "node_modules/npm/node_modules/@colors/colors": { "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -8073,24 +8082,18 @@ }, "node_modules/npm/node_modules/@gar/promisify": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/@isaacs/string-locale-compare": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz", - "integrity": "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==", "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/@npmcli/arborist": { "version": "5.6.3", - "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-5.6.3.tgz", - "integrity": "sha512-/7hbqEM6YuRjwTcQXkK1+xKslEblY5kFQe0tZ7jKyMlIR6x4iOmhLErIkBBGtTKvYxRKdpcxnFXjCobg3UqmsA==", "dev": true, "inBundle": true, "license": "ISC", @@ -8142,8 +8145,6 @@ }, "node_modules/npm/node_modules/@npmcli/ci-detect": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/ci-detect/-/ci-detect-2.0.0.tgz", - "integrity": "sha512-8yQtQ9ArHh/TzdUDKQwEvwCgpDuhSWTDAbiKMl3854PcT+Dk4UmWaiawuFTLy9n5twzXOBXVflWe+90/ffXQrA==", "dev": true, "inBundle": true, "license": "ISC", @@ -8153,8 +8154,6 @@ }, "node_modules/npm/node_modules/@npmcli/config": { "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@npmcli/config/-/config-4.2.2.tgz", - "integrity": "sha512-5GNcLd+0c4bYBnFop53+26CO5GQP0R9YcxlernohpHDWdIgzUg9I0+GEMk3sNHnLntATVU39d283A4OO+W402w==", "dev": true, "inBundle": true, "license": "ISC", @@ -8174,8 +8173,6 @@ }, "node_modules/npm/node_modules/@npmcli/disparity-colors": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/disparity-colors/-/disparity-colors-2.0.0.tgz", - "integrity": "sha512-FFXGrIjhvd2qSZ8iS0yDvbI7nbjdyT2VNO7wotosjYZM2p2r8PN3B7Om3M5NO9KqW/OVzfzLB3L0V5Vo5QXC7A==", "dev": true, "inBundle": true, "license": "ISC", @@ -8188,8 +8185,6 @@ }, "node_modules/npm/node_modules/@npmcli/fs": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", - "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", "dev": true, "inBundle": true, "license": "ISC", @@ -8203,8 +8198,6 @@ }, "node_modules/npm/node_modules/@npmcli/git": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-3.0.2.tgz", - "integrity": "sha512-CAcd08y3DWBJqJDpfuVL0uijlq5oaXaOJEKHKc4wqrjd00gkvTZB+nFuLn+doOOKddaQS9JfqtNoFCO2LCvA3w==", "dev": true, "inBundle": true, "license": "ISC", @@ -8225,8 +8218,6 @@ }, "node_modules/npm/node_modules/@npmcli/installed-package-contents": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz", - "integrity": "sha512-9rufe0wnJusCQoLpV9ZPKIVP55itrM5BxOXs10DmdbRfgWtHy1LDyskbwRnBghuB0PrF7pNPOqREVtpz4HqzKw==", "dev": true, "inBundle": true, "license": "ISC", @@ -8252,8 +8243,6 @@ }, "node_modules/npm/node_modules/@npmcli/map-workspaces": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-2.0.4.tgz", - "integrity": "sha512-bMo0aAfwhVwqoVM5UzX1DJnlvVvzDCHae821jv48L1EsrYwfOZChlqWYXEtto/+BkBXetPbEWgau++/brh4oVg==", "dev": true, "inBundle": true, "license": "ISC", @@ -8269,8 +8258,6 @@ }, "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/metavuln-calculator/-/metavuln-calculator-3.1.1.tgz", - "integrity": "sha512-n69ygIaqAedecLeVH3KnO39M6ZHiJ2dEv5A7DGvcqCB8q17BGUgW8QaanIkbWUo2aYGZqJaOORTLAlIvKjNDKA==", "dev": true, "inBundle": true, "license": "ISC", @@ -8286,8 +8273,6 @@ }, "node_modules/npm/node_modules/@npmcli/move-file": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", - "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -8301,16 +8286,12 @@ }, "node_modules/npm/node_modules/@npmcli/name-from-folder": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-1.0.1.tgz", - "integrity": "sha512-qq3oEfcLFwNfEYOQ8HLimRGKlD8WSeGEdtUa7hmzpR8Sa7haL1KVQrvgO6wqMjhWFFVjgtrh1gIxDz+P8sjUaA==", "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/@npmcli/node-gyp": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-2.0.0.tgz", - "integrity": "sha512-doNI35wIe3bBaEgrlPfdJPaCpUR89pJWep4Hq3aRdh6gKazIVWfs0jHttvSSoq47ZXgC7h73kDsUl8AoIQUB+A==", "dev": true, "inBundle": true, "license": "ISC", @@ -8320,8 +8301,6 @@ }, "node_modules/npm/node_modules/@npmcli/package-json": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-2.0.0.tgz", - "integrity": "sha512-42jnZ6yl16GzjWSH7vtrmWyJDGVa/LXPdpN2rcUWolFjc9ON2N3uz0qdBbQACfmhuJZ2lbKYtmK5qx68ZPLHMA==", "dev": true, "inBundle": true, "license": "ISC", @@ -8334,8 +8313,6 @@ }, "node_modules/npm/node_modules/@npmcli/promise-spawn": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-3.0.0.tgz", - "integrity": "sha512-s9SgS+p3a9Eohe68cSI3fi+hpcZUmXq5P7w0kMlAsWVtR7XbK3ptkZqKT2cK1zLDObJ3sR+8P59sJE0w/KTL1g==", "dev": true, "inBundle": true, "license": "ISC", @@ -8348,8 +8325,6 @@ }, "node_modules/npm/node_modules/@npmcli/query": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@npmcli/query/-/query-1.2.0.tgz", - "integrity": "sha512-uWglsUM3PjBLgTSmZ3/vygeGdvWEIZ3wTUnzGFbprC/RtvQSaT+GAXu1DXmSFj2bD3oOZdcRm1xdzsV2z1YWdw==", "dev": true, "inBundle": true, "license": "ISC", @@ -8364,8 +8339,6 @@ }, "node_modules/npm/node_modules/@npmcli/run-script": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-4.2.1.tgz", - "integrity": "sha512-7dqywvVudPSrRCW5nTHpHgeWnbBtz8cFkOuKrecm6ih+oO9ciydhWt6OF7HlqupRRmB8Q/gECVdB9LMfToJbRg==", "dev": true, "inBundle": true, "license": "ISC", @@ -8382,8 +8355,6 @@ }, "node_modules/npm/node_modules/@tootallnate/once": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", "dev": true, "inBundle": true, "license": "MIT", @@ -8393,16 +8364,12 @@ }, "node_modules/npm/node_modules/abbrev": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/agent-base": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -8415,8 +8382,6 @@ }, "node_modules/npm/node_modules/agentkeepalive": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz", - "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==", "dev": true, "inBundle": true, "license": "MIT", @@ -8431,8 +8396,6 @@ }, "node_modules/npm/node_modules/aggregate-error": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", "dev": true, "inBundle": true, "license": "MIT", @@ -8446,8 +8409,6 @@ }, "node_modules/npm/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, "inBundle": true, "license": "MIT", @@ -8457,8 +8418,6 @@ }, "node_modules/npm/node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "inBundle": true, "license": "MIT", @@ -8474,24 +8433,18 @@ }, "node_modules/npm/node_modules/aproba": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/archy": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/are-we-there-yet": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", "dev": true, "inBundle": true, "license": "ISC", @@ -8505,24 +8458,18 @@ }, "node_modules/npm/node_modules/asap": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/balanced-match": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/bin-links": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-3.0.3.tgz", - "integrity": "sha512-zKdnMPWEdh4F5INR07/eBrodC7QrF5JKvqskjz/ZZRXg5YSAZIbn8zGhbhUrElzHBZ2fvEQdOU59RHcTG3GiwA==", "dev": true, "inBundle": true, "license": "ISC", @@ -8549,8 +8496,6 @@ }, "node_modules/npm/node_modules/binary-extensions": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true, "inBundle": true, "license": "MIT", @@ -8560,8 +8505,6 @@ }, "node_modules/npm/node_modules/brace-expansion": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "inBundle": true, "license": "MIT", @@ -8571,8 +8514,6 @@ }, "node_modules/npm/node_modules/builtins": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", - "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -8582,8 +8523,6 @@ }, "node_modules/npm/node_modules/cacache": { "version": "16.1.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", - "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", "dev": true, "inBundle": true, "license": "ISC", @@ -8613,8 +8552,6 @@ }, "node_modules/npm/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, "inBundle": true, "license": "MIT", @@ -8631,8 +8568,6 @@ }, "node_modules/npm/node_modules/chownr": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", "dev": true, "inBundle": true, "license": "ISC", @@ -8642,8 +8577,6 @@ }, "node_modules/npm/node_modules/cidr-regex": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/cidr-regex/-/cidr-regex-3.1.1.tgz", - "integrity": "sha512-RBqYd32aDwbCMFJRL6wHOlDNYJsPNTt8vC82ErHF5vKt8QQzxm1FrkW8s/R5pVrXMf17sba09Uoy91PKiddAsw==", "dev": true, "inBundle": true, "license": "BSD-2-Clause", @@ -8656,8 +8589,6 @@ }, "node_modules/npm/node_modules/clean-stack": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true, "inBundle": true, "license": "MIT", @@ -8667,8 +8598,6 @@ }, "node_modules/npm/node_modules/cli-columns": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-columns/-/cli-columns-4.0.0.tgz", - "integrity": "sha512-XW2Vg+w+L9on9wtwKpyzluIPCWXjaBahI7mTcYjx+BVIYD9c3yqcv/yKC7CmdCZat4rq2yiE1UMSJC5ivKfMtQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -8697,8 +8626,6 @@ }, "node_modules/npm/node_modules/clone": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "dev": true, "inBundle": true, "license": "MIT", @@ -8708,8 +8635,6 @@ }, "node_modules/npm/node_modules/cmd-shim": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-5.0.0.tgz", - "integrity": "sha512-qkCtZ59BidfEwHltnJwkyVZn+XQojdAySM1D1gSeh11Z4pW1Kpolkyo53L5noc0nrxmIvyFwTmJRo4xs7FFLPw==", "dev": true, "inBundle": true, "license": "ISC", @@ -8722,8 +8647,6 @@ }, "node_modules/npm/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, "inBundle": true, "license": "MIT", @@ -8736,16 +8659,12 @@ }, "node_modules/npm/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, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/color-support": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", "dev": true, "inBundle": true, "license": "ISC", @@ -8755,8 +8674,6 @@ }, "node_modules/npm/node_modules/columnify": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.6.0.tgz", - "integrity": "sha512-lomjuFZKfM6MSAnV9aCZC9sc0qGbmZdfygNv+nCpqVkSKdCxCklLtd16O0EILGkImHw9ZpHkAnHaB+8Zxq5W6Q==", "dev": true, "inBundle": true, "license": "MIT", @@ -8770,32 +8687,24 @@ }, "node_modules/npm/node_modules/common-ancestor-path": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", - "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/concat-map": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/console-control-strings": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/cssesc": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true, "inBundle": true, "license": "MIT", @@ -8808,8 +8717,6 @@ }, "node_modules/npm/node_modules/debug": { "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -8833,8 +8740,6 @@ }, "node_modules/npm/node_modules/debuglog": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", - "integrity": "sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw==", "dev": true, "inBundle": true, "license": "MIT", @@ -8853,16 +8758,12 @@ }, "node_modules/npm/node_modules/delegates": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/depd": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -8872,8 +8773,6 @@ }, "node_modules/npm/node_modules/dezalgo": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", - "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", "dev": true, "inBundle": true, "license": "ISC", @@ -8884,8 +8783,6 @@ }, "node_modules/npm/node_modules/diff": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", - "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", "dev": true, "inBundle": true, "license": "BSD-3-Clause", @@ -8895,16 +8792,12 @@ }, "node_modules/npm/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, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/encoding": { "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "dev": true, "inBundle": true, "license": "MIT", @@ -8915,8 +8808,6 @@ }, "node_modules/npm/node_modules/env-paths": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true, "inBundle": true, "license": "MIT", @@ -8926,24 +8817,18 @@ }, "node_modules/npm/node_modules/err-code": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/fastest-levenshtein": { "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", - "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/fs-minipass": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", "dev": true, "inBundle": true, "license": "ISC", @@ -8956,24 +8841,18 @@ }, "node_modules/npm/node_modules/fs.realpath": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/function-bind": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/gauge": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", "dev": true, "inBundle": true, "license": "ISC", @@ -8993,8 +8872,6 @@ }, "node_modules/npm/node_modules/glob": { "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", "dev": true, "inBundle": true, "license": "ISC", @@ -9014,16 +8891,12 @@ }, "node_modules/npm/node_modules/graceful-fs": { "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/has": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "inBundle": true, "license": "MIT", @@ -9036,8 +8909,6 @@ }, "node_modules/npm/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, "inBundle": true, "license": "MIT", @@ -9047,16 +8918,12 @@ }, "node_modules/npm/node_modules/has-unicode": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/hosted-git-info": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-5.2.1.tgz", - "integrity": "sha512-xIcQYMnhcx2Nr4JTjsFmwwnr9vldugPy9uVm0o87bjqqWMv9GaqsTeT+i99wTl0mk1uLxJtHxLb8kymqTENQsw==", "dev": true, "inBundle": true, "license": "ISC", @@ -9069,16 +8936,12 @@ }, "node_modules/npm/node_modules/http-cache-semantics": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", "dev": true, "inBundle": true, "license": "BSD-2-Clause" }, "node_modules/npm/node_modules/http-proxy-agent": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "dev": true, "inBundle": true, "license": "MIT", @@ -9106,8 +8969,6 @@ }, "node_modules/npm/node_modules/humanize-ms": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -9117,8 +8978,6 @@ }, "node_modules/npm/node_modules/iconv-lite": { "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "inBundle": true, "license": "MIT", @@ -9132,8 +8991,6 @@ }, "node_modules/npm/node_modules/ignore-walk": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-5.0.1.tgz", - "integrity": "sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw==", "dev": true, "inBundle": true, "license": "ISC", @@ -9146,8 +9003,6 @@ }, "node_modules/npm/node_modules/imurmurhash": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true, "inBundle": true, "license": "MIT", @@ -9157,8 +9012,6 @@ }, "node_modules/npm/node_modules/indent-string": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true, "inBundle": true, "license": "MIT", @@ -9168,16 +9021,12 @@ }, "node_modules/npm/node_modules/infer-owner": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/inflight": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "inBundle": true, "license": "ISC", @@ -9188,16 +9037,12 @@ }, "node_modules/npm/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, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/ini": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-3.0.1.tgz", - "integrity": "sha512-it4HyVAUTKBc6m8e1iXWvXSTdndF7HbdN713+kvLrymxTaU4AUBWrJ4vEooP+V7fexnVD3LKcBshjGGPefSMUQ==", "dev": true, "inBundle": true, "license": "ISC", @@ -9207,8 +9052,6 @@ }, "node_modules/npm/node_modules/init-package-json": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-3.0.2.tgz", - "integrity": "sha512-YhlQPEjNFqlGdzrBfDNRLhvoSgX7iQRgSxgsNknRQ9ITXFT7UMfVMWhBTOh2Y+25lRnGrv5Xz8yZwQ3ACR6T3A==", "dev": true, "inBundle": true, "license": "ISC", @@ -9227,16 +9070,12 @@ }, "node_modules/npm/node_modules/ip": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/ip-regex": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", - "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", "dev": true, "inBundle": true, "license": "MIT", @@ -9246,8 +9085,6 @@ }, "node_modules/npm/node_modules/is-cidr": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/is-cidr/-/is-cidr-4.0.2.tgz", - "integrity": "sha512-z4a1ENUajDbEl/Q6/pVBpTR1nBjjEE1X7qb7bmWYanNnPoKAvUCPFKeXV6Fe4mgTkWKBqiHIcwsI3SndiO5FeA==", "dev": true, "inBundle": true, "license": "BSD-2-Clause", @@ -9272,8 +9109,6 @@ }, "node_modules/npm/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, "inBundle": true, "license": "MIT", @@ -9283,32 +9118,24 @@ }, "node_modules/npm/node_modules/is-lambda": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/isexe": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/json-parse-even-better-errors": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/json-stringify-nice": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz", - "integrity": "sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw==", "dev": true, "inBundle": true, "license": "ISC", @@ -9318,8 +9145,6 @@ }, "node_modules/npm/node_modules/jsonparse": { "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", "dev": true, "engines": [ "node >= 0.2.0" @@ -9341,8 +9166,6 @@ }, "node_modules/npm/node_modules/libnpmaccess": { "version": "6.0.4", - "resolved": "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-6.0.4.tgz", - "integrity": "sha512-qZ3wcfIyUoW0+qSFkMBovcTrSGJ3ZeyvpR7d5N9pEYv/kXs8sHP2wiqEIXBKLFrZlmM0kR0RJD7mtfLngtlLag==", "dev": true, "inBundle": true, "license": "ISC", @@ -9358,8 +9181,6 @@ }, "node_modules/npm/node_modules/libnpmdiff": { "version": "4.0.5", - "resolved": "https://registry.npmjs.org/libnpmdiff/-/libnpmdiff-4.0.5.tgz", - "integrity": "sha512-9fICQIzmH892UwHHPmb+Seup50UIBWcMIK2FdxvlXm9b4kc1nSH0b/BuY1mORJQtB6ydPMnn+BLzOTmd/SKJmw==", "dev": true, "inBundle": true, "license": "ISC", @@ -9379,8 +9200,6 @@ }, "node_modules/npm/node_modules/libnpmexec": { "version": "4.0.14", - "resolved": "https://registry.npmjs.org/libnpmexec/-/libnpmexec-4.0.14.tgz", - "integrity": "sha512-dwmzv2K29SdoAHBOa7QR6CfQbFG/PiZDRF6HZrlI6C4DLt2hNgOHTFaUGOpqE2C+YGu0ZwYTDywxRe0eOnf0ZA==", "dev": true, "inBundle": true, "license": "ISC", @@ -9406,8 +9225,6 @@ }, "node_modules/npm/node_modules/libnpmfund": { "version": "3.0.5", - "resolved": "https://registry.npmjs.org/libnpmfund/-/libnpmfund-3.0.5.tgz", - "integrity": "sha512-KdeRoG/dem8H3PcEU2/0SKi3ip7AWwczgS72y/3PE+PBrz/s/G52FNIA9jeLnBirkLC0sOyQHfeM3b7e24ZM+g==", "dev": true, "inBundle": true, "license": "ISC", @@ -9420,8 +9237,6 @@ }, "node_modules/npm/node_modules/libnpmhook": { "version": "8.0.4", - "resolved": "https://registry.npmjs.org/libnpmhook/-/libnpmhook-8.0.4.tgz", - "integrity": "sha512-nuD6e+Nx0OprjEi0wOeqASMl6QIH235th/Du2/8upK3evByFhzIgdfOeP1OhstavW4xtsl0hk5Vw4fAWWuSUgA==", "dev": true, "inBundle": true, "license": "ISC", @@ -9435,8 +9250,6 @@ }, "node_modules/npm/node_modules/libnpmorg": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/libnpmorg/-/libnpmorg-4.0.4.tgz", - "integrity": "sha512-1bTpD7iub1rDCsgiBguhJhiDufLQuc8DEti20euqsXz9O0ncXVpCYqf2SMmHR4GEdmAvAj2r7FMiyA9zGdaTpA==", "dev": true, "inBundle": true, "license": "ISC", @@ -9450,8 +9263,6 @@ }, "node_modules/npm/node_modules/libnpmpack": { "version": "4.1.3", - "resolved": "https://registry.npmjs.org/libnpmpack/-/libnpmpack-4.1.3.tgz", - "integrity": "sha512-rYP4X++ME3ZiFO+2iN3YnXJ4LB4Gsd0z5cgszWJZxaEpDN4lRIXirSyynGNsN/hn4taqnlxD+3DPlFDShvRM8w==", "dev": true, "inBundle": true, "license": "ISC", @@ -9466,8 +9277,6 @@ }, "node_modules/npm/node_modules/libnpmpublish": { "version": "6.0.5", - "resolved": "https://registry.npmjs.org/libnpmpublish/-/libnpmpublish-6.0.5.tgz", - "integrity": "sha512-LUR08JKSviZiqrYTDfywvtnsnxr+tOvBU0BF8H+9frt7HMvc6Qn6F8Ubm72g5hDTHbq8qupKfDvDAln2TVPvFg==", "dev": true, "inBundle": true, "license": "ISC", @@ -9484,8 +9293,6 @@ }, "node_modules/npm/node_modules/libnpmsearch": { "version": "5.0.4", - "resolved": "https://registry.npmjs.org/libnpmsearch/-/libnpmsearch-5.0.4.tgz", - "integrity": "sha512-XHDmsvpN5+pufvGnfLRqpy218gcGGbbbXR6wPrDJyd1em6agKdYByzU5ccskDHH9iVm2UeLydpDsW1ksYuU0cg==", "dev": true, "inBundle": true, "license": "ISC", @@ -9498,8 +9305,6 @@ }, "node_modules/npm/node_modules/libnpmteam": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/libnpmteam/-/libnpmteam-4.0.4.tgz", - "integrity": "sha512-rzKSwi6MLzwwevbM/vl+BBQTErgn24tCfgPUdzBlszrw3j5necOu7WnTzgvZMDv6maGUwec6Ut1rxszOgH0l+Q==", "dev": true, "inBundle": true, "license": "ISC", @@ -9513,8 +9318,6 @@ }, "node_modules/npm/node_modules/libnpmversion": { "version": "3.0.7", - "resolved": "https://registry.npmjs.org/libnpmversion/-/libnpmversion-3.0.7.tgz", - "integrity": "sha512-O0L4eNMUIMQ+effi1HsZPKp2N6wecwqGqB8PvkvmLPWN7EsdabdzAVG48nv0p/OjlbIai5KQg/L+qMMfCA4ZjA==", "dev": true, "inBundle": true, "license": "ISC", @@ -9540,8 +9343,6 @@ }, "node_modules/npm/node_modules/make-fetch-happen": { "version": "10.2.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", - "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", "dev": true, "inBundle": true, "license": "ISC", @@ -9593,8 +9394,6 @@ }, "node_modules/npm/node_modules/minipass-collect": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", "dev": true, "inBundle": true, "license": "ISC", @@ -9624,8 +9423,6 @@ }, "node_modules/npm/node_modules/minipass-flush": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", "dev": true, "inBundle": true, "license": "ISC", @@ -9638,8 +9435,6 @@ }, "node_modules/npm/node_modules/minipass-json-stream": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", - "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", "dev": true, "inBundle": true, "license": "MIT", @@ -9650,8 +9445,6 @@ }, "node_modules/npm/node_modules/minipass-pipeline": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", "dev": true, "inBundle": true, "license": "ISC", @@ -9664,8 +9457,6 @@ }, "node_modules/npm/node_modules/minipass-sized": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", "dev": true, "inBundle": true, "license": "ISC", @@ -9678,8 +9469,6 @@ }, "node_modules/npm/node_modules/minizlib": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "dev": true, "inBundle": true, "license": "MIT", @@ -9693,8 +9482,6 @@ }, "node_modules/npm/node_modules/mkdirp": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true, "inBundle": true, "license": "MIT", @@ -9707,8 +9494,6 @@ }, "node_modules/npm/node_modules/mkdirp-infer-owner": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mkdirp-infer-owner/-/mkdirp-infer-owner-2.0.0.tgz", - "integrity": "sha512-sdqtiFt3lkOaYvTXSRIUjkIdPTcxgv5+fgqYE/5qgwdw12cOrAuzzgzvVExIkH/ul1oeHN3bCLOWSG3XOqbKKw==", "dev": true, "inBundle": true, "license": "ISC", @@ -9723,24 +9508,18 @@ }, "node_modules/npm/node_modules/ms": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/mute-stream": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/negotiator": { "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "dev": true, "inBundle": true, "license": "MIT", @@ -9774,8 +9553,6 @@ }, "node_modules/npm/node_modules/node-gyp/node_modules/brace-expansion": { "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "inBundle": true, "license": "MIT", @@ -9806,8 +9583,6 @@ }, "node_modules/npm/node_modules/node-gyp/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, "inBundle": true, "license": "ISC", @@ -9835,8 +9610,6 @@ }, "node_modules/npm/node_modules/nopt": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", - "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", "dev": true, "inBundle": true, "license": "ISC", @@ -9852,8 +9625,6 @@ }, "node_modules/npm/node_modules/normalize-package-data": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-4.0.1.tgz", - "integrity": "sha512-EBk5QKKuocMJhB3BILuKhmaPjI8vNRSpIfO9woLC6NyHVkKKdVEdAO1mrT0ZfxNR1lKwCcTkuZfmGIFdizZ8Pg==", "dev": true, "inBundle": true, "license": "BSD-2-Clause", @@ -9869,8 +9640,6 @@ }, "node_modules/npm/node_modules/npm-audit-report": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/npm-audit-report/-/npm-audit-report-3.0.0.tgz", - "integrity": "sha512-tWQzfbwz1sc4244Bx2BVELw0EmZlCsCF0X93RDcmmwhonCsPMoEviYsi+32R+mdRvOWXolPce9zo64n2xgPESw==", "dev": true, "inBundle": true, "license": "ISC", @@ -9883,8 +9652,6 @@ }, "node_modules/npm/node_modules/npm-bundled": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-2.0.1.tgz", - "integrity": "sha512-gZLxXdjEzE/+mOstGDqR6b0EkhJ+kM6fxM6vUuckuctuVPh80Q6pw/rSZj9s4Gex9GxWtIicO1pc8DB9KZWudw==", "dev": true, "inBundle": true, "license": "ISC", @@ -9906,8 +9673,6 @@ }, "node_modules/npm/node_modules/npm-install-checks": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-5.0.0.tgz", - "integrity": "sha512-65lUsMI8ztHCxFz5ckCEC44DRvEGdZX5usQFriauxHEwt7upv1FKaQEmAtU0YnOAdwuNWCmk64xYiQABNrEyLA==", "dev": true, "inBundle": true, "license": "BSD-2-Clause", @@ -9920,8 +9685,6 @@ }, "node_modules/npm/node_modules/npm-normalize-package-bin": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", - "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", "dev": true, "inBundle": true, "license": "ISC" @@ -9943,8 +9706,6 @@ }, "node_modules/npm/node_modules/npm-packlist": { "version": "5.1.3", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-5.1.3.tgz", - "integrity": "sha512-263/0NGrn32YFYi4J533qzrQ/krmmrWwhKkzwTuM4f/07ug51odoaNjUexxO4vxlzURHcmYMH1QjvHjsNDKLVg==", "dev": true, "inBundle": true, "license": "ISC", @@ -9972,8 +9733,6 @@ }, "node_modules/npm/node_modules/npm-pick-manifest": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-7.0.2.tgz", - "integrity": "sha512-gk37SyRmlIjvTfcYl6RzDbSmS9Y4TOBXfsPnoYqTHARNgWbyDiCSMLUpmALDj4jjcTZpURiEfsSHJj9k7EV4Rw==", "dev": true, "inBundle": true, "license": "ISC", @@ -9998,8 +9757,6 @@ }, "node_modules/npm/node_modules/npm-profile": { "version": "6.2.1", - "resolved": "https://registry.npmjs.org/npm-profile/-/npm-profile-6.2.1.tgz", - "integrity": "sha512-Tlu13duByHyDd4Xy0PgroxzxnBYWbGGL5aZifNp8cx2DxUrHSoETXtPKg38aRPsBWMRfDtvcvVfJNasj7oImQQ==", "dev": true, "inBundle": true, "license": "ISC", @@ -10013,8 +9770,6 @@ }, "node_modules/npm/node_modules/npm-registry-fetch": { "version": "13.3.1", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-13.3.1.tgz", - "integrity": "sha512-eukJPi++DKRTjSBRcDZSDDsGqRK3ehbxfFUcgaRd0Yp6kRwOwh2WVn0r+8rMB4nnuzvAk6rQVzl6K5CkYOmnvw==", "dev": true, "inBundle": true, "license": "ISC", @@ -10033,16 +9788,12 @@ }, "node_modules/npm/node_modules/npm-user-validate": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-user-validate/-/npm-user-validate-1.0.1.tgz", - "integrity": "sha512-uQwcd/tY+h1jnEaze6cdX/LrhWhoBxfSknxentoqmIuStxUExxjWd3ULMLFPiFUrZKbOVMowH6Jq2FRWfmhcEw==", "dev": true, "inBundle": true, "license": "BSD-2-Clause" }, "node_modules/npm/node_modules/npmlog": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", "dev": true, "inBundle": true, "license": "ISC", @@ -10058,8 +9809,6 @@ }, "node_modules/npm/node_modules/once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "inBundle": true, "license": "ISC", @@ -10069,8 +9818,6 @@ }, "node_modules/npm/node_modules/opener": { "version": "1.5.2", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", - "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", "dev": true, "inBundle": true, "license": "(WTFPL OR MIT)", @@ -10080,8 +9827,6 @@ }, "node_modules/npm/node_modules/p-map": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -10097,8 +9842,6 @@ }, "node_modules/npm/node_modules/pacote": { "version": "13.6.2", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-13.6.2.tgz", - "integrity": "sha512-Gu8fU3GsvOPkak2CkbojR7vjs3k3P9cA6uazKTHdsdV0gpCEQq2opelnEv30KRQWgVzP5Vd/5umjcedma3MKtg==", "dev": true, "inBundle": true, "license": "ISC", @@ -10134,8 +9877,6 @@ }, "node_modules/npm/node_modules/parse-conflict-json": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-2.0.2.tgz", - "integrity": "sha512-jDbRGb00TAPFsKWCpZZOT93SxVP9nONOSgES3AevqRq/CHvavEBvKAjxX9p5Y5F0RZLxH9Ufd9+RwtCsa+lFDA==", "dev": true, "inBundle": true, "license": "ISC", @@ -10150,8 +9891,6 @@ }, "node_modules/npm/node_modules/path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true, "inBundle": true, "license": "MIT", @@ -10174,8 +9913,6 @@ }, "node_modules/npm/node_modules/proc-log": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz", - "integrity": "sha512-Kcmo2FhfDTXdcbfDH76N7uBYHINxc/8GW7UAVuVP9I+Va3uHSerrnKV6dLooga/gh7GlgzuCCr/eoldnL1muGw==", "dev": true, "inBundle": true, "license": "ISC", @@ -10185,8 +9922,6 @@ }, "node_modules/npm/node_modules/promise-all-reject-late": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz", - "integrity": "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw==", "dev": true, "inBundle": true, "license": "ISC", @@ -10196,8 +9931,6 @@ }, "node_modules/npm/node_modules/promise-call-limit": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-1.0.1.tgz", - "integrity": "sha512-3+hgaa19jzCGLuSCbieeRsu5C2joKfYn8pY6JAuXFRVfF4IO+L7UPpFWNTeWT9pM7uhskvbPPd/oEOktCn317Q==", "dev": true, "inBundle": true, "license": "ISC", @@ -10207,16 +9940,12 @@ }, "node_modules/npm/node_modules/promise-inflight": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/promise-retry": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", "dev": true, "inBundle": true, "license": "MIT", @@ -10230,8 +9959,6 @@ }, "node_modules/npm/node_modules/promzard": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/promzard/-/promzard-0.3.0.tgz", - "integrity": "sha512-JZeYqd7UAcHCwI+sTOeUDYkvEU+1bQ7iE0UT1MgB/tERkAPkesW46MrpIySzODi+owTjZtiF8Ay5j9m60KmMBw==", "dev": true, "inBundle": true, "license": "ISC", @@ -10241,8 +9968,6 @@ }, "node_modules/npm/node_modules/qrcode-terminal": { "version": "0.12.0", - "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", - "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==", "dev": true, "inBundle": true, "bin": { @@ -10251,8 +9976,6 @@ }, "node_modules/npm/node_modules/read": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", - "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", "dev": true, "inBundle": true, "license": "ISC", @@ -10274,8 +9997,6 @@ }, "node_modules/npm/node_modules/read-package-json": { "version": "5.0.2", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-5.0.2.tgz", - "integrity": "sha512-BSzugrt4kQ/Z0krro8zhTwV1Kd79ue25IhNN/VtHFy1mG/6Tluyi+msc0UpwaoQzxSHa28mntAjIZY6kEgfR9Q==", "dev": true, "inBundle": true, "license": "ISC", @@ -10291,8 +10012,6 @@ }, "node_modules/npm/node_modules/read-package-json-fast": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz", - "integrity": "sha512-W/BKtbL+dUjTuRL2vziuYhp76s5HZ9qQhd/dKfWIZveD0O40453QNyZhC0e63lqZrAQ4jiOapVoeJ7JrszenQQ==", "dev": true, "inBundle": true, "license": "ISC", @@ -10315,8 +10034,6 @@ }, "node_modules/npm/node_modules/readable-stream": { "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, "inBundle": true, "license": "MIT", @@ -10331,8 +10048,6 @@ }, "node_modules/npm/node_modules/readdir-scoped-modules": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz", - "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==", "dev": true, "inBundle": true, "license": "ISC", @@ -10345,8 +10060,6 @@ }, "node_modules/npm/node_modules/retry": { "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", "dev": true, "inBundle": true, "license": "MIT", @@ -10356,8 +10069,6 @@ }, "node_modules/npm/node_modules/rimraf": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "inBundle": true, "license": "ISC", @@ -10373,8 +10084,6 @@ }, "node_modules/npm/node_modules/rimraf/node_modules/brace-expansion": { "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "inBundle": true, "license": "MIT", @@ -10405,8 +10114,6 @@ }, "node_modules/npm/node_modules/rimraf/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, "inBundle": true, "license": "ISC", @@ -10419,8 +10126,6 @@ }, "node_modules/npm/node_modules/safe-buffer": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true, "funding": [ { @@ -10441,8 +10146,6 @@ }, "node_modules/npm/node_modules/safer-buffer": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, "inBundle": true, "license": "MIT", @@ -10477,24 +10180,18 @@ }, "node_modules/npm/node_modules/set-blocking": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/signal-exit": { "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/smart-buffer": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", "dev": true, "inBundle": true, "license": "MIT", @@ -10519,8 +10216,6 @@ }, "node_modules/npm/node_modules/socks-proxy-agent": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", - "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", "dev": true, "inBundle": true, "license": "MIT", @@ -10535,8 +10230,6 @@ }, "node_modules/npm/node_modules/spdx-correct": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", "dev": true, "inBundle": true, "license": "Apache-2.0", @@ -10547,16 +10240,12 @@ }, "node_modules/npm/node_modules/spdx-exceptions": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", "dev": true, "inBundle": true, "license": "CC-BY-3.0" }, "node_modules/npm/node_modules/spdx-expression-parse": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "inBundle": true, "license": "MIT", @@ -10573,8 +10262,6 @@ }, "node_modules/npm/node_modules/ssri": { "version": "9.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", - "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", "dev": true, "inBundle": true, "license": "ISC", @@ -10587,8 +10274,6 @@ }, "node_modules/npm/node_modules/string_decoder": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "inBundle": true, "license": "MIT", @@ -10598,8 +10283,6 @@ }, "node_modules/npm/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, "inBundle": true, "license": "MIT", @@ -10614,8 +10297,6 @@ }, "node_modules/npm/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, "inBundle": true, "license": "MIT", @@ -10628,8 +10309,6 @@ }, "node_modules/npm/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, "inBundle": true, "license": "MIT", @@ -10659,24 +10338,18 @@ }, "node_modules/npm/node_modules/text-table": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/tiny-relative-date": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz", - "integrity": "sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/treeverse": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/treeverse/-/treeverse-2.0.0.tgz", - "integrity": "sha512-N5gJCkLu1aXccpOTtqV6ddSEi6ZmGkh3hjmbu1IjcavJK4qyOVQmi0myQKM7z5jVGmD68SJoliaVrMmVObhj6A==", "dev": true, "inBundle": true, "license": "ISC", @@ -10686,8 +10359,6 @@ }, "node_modules/npm/node_modules/unique-filename": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", - "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", "dev": true, "inBundle": true, "license": "ISC", @@ -10700,8 +10371,6 @@ }, "node_modules/npm/node_modules/unique-slug": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", - "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", "dev": true, "inBundle": true, "license": "ISC", @@ -10714,16 +10383,12 @@ }, "node_modules/npm/node_modules/util-deprecate": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/validate-npm-package-license": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "inBundle": true, "license": "Apache-2.0", @@ -10734,8 +10399,6 @@ }, "node_modules/npm/node_modules/validate-npm-package-name": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-4.0.0.tgz", - "integrity": "sha512-mzR0L8ZDktZjpX4OB46KT+56MAhl4EIazWP/+G/HPGuvfdaqg4YsCdtOm6U9+LOFyYDoh4dpnpxZRB9MQQns5Q==", "dev": true, "inBundle": true, "license": "ISC", @@ -10748,16 +10411,12 @@ }, "node_modules/npm/node_modules/walk-up-path": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-1.0.0.tgz", - "integrity": "sha512-hwj/qMDUEjCU5h0xr90KGCf0tg0/LgJbmOWgrWKYlcJZM7XvquvUJZ0G/HMGr7F7OQMOUuPHWP9JpriinkAlkg==", "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/wcwidth": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", "dev": true, "inBundle": true, "license": "MIT", @@ -10767,8 +10426,6 @@ }, "node_modules/npm/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, "inBundle": true, "license": "ISC", @@ -10784,8 +10441,6 @@ }, "node_modules/npm/node_modules/wide-align": { "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", "dev": true, "inBundle": true, "license": "ISC", @@ -10795,16 +10450,12 @@ }, "node_modules/npm/node_modules/wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/write-file-atomic": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, "inBundle": true, "license": "ISC", @@ -10818,8 +10469,6 @@ }, "node_modules/npm/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "inBundle": true, "license": "ISC" @@ -11468,6 +11117,7 @@ "integrity": "sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin-prettier.js" }, @@ -12082,6 +11732,7 @@ "integrity": "sha512-NMPKdfpXTnPn49FDogMBi36SiBfXkSOJqCkk0E4iWOY1tusvvgBwqUmxTX1kmlT6kIYed9YwNKD1sfPpqa5yaA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^9.0.2", "@semantic-release/error": "^3.0.0", @@ -13472,6 +13123,7 @@ "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -14018,6 +13670,7 @@ "integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^0.0.51", @@ -14066,6 +13719,7 @@ "integrity": "sha512-S3KVAyfwUqr0Mo/ur3NzIp6jnerNpo7GUO6so51mxLi1spqsA17YcMXy0WOIJtBSnj748lthxC6XLbNKh/ZC+A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^2.0.1", diff --git a/package.json b/package.json index dbbdd5d..616cabf 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,8 @@ "dev-install": "npm run compile && npx vsce package && code --install-extension codebot-1.0.0.vsix --force" }, "engines": { - "vscode": "^1.74.0" + "vscode": "^1.74.0", + "node": ">=24.12.0" }, "dependencies": { "fs": "^0.0.2", @@ -92,4 +93,4 @@ "webpack": "^5.75.0", "webpack-cli": "^5.0.1" } -} \ No newline at end of file +} From 4040a7ae74dd3e2b14f3319af15128eb6f0222e6 Mon Sep 17 00:00:00 2001 From: Douglas Gomes Date: Fri, 17 Apr 2026 16:40:50 -0300 Subject: [PATCH 02/17] chore: migrate from ESLint and Prettier to Biome - Removed .eslintrc.json and .prettierrc.json configuration files. - Added biome.json for Biome configuration. - Updated package.json to replace ESLint and Prettier commands with Biome commands. - Updated package-lock.json to include @biomejs/biome as a new dependency. --- .eslintrc.json | 26 - .prettierrc.json | 10 - biome.json | 28 + package-lock.json | 1667 +++++---------------------------------------- package.json | 13 +- 5 files changed, 204 insertions(+), 1540 deletions(-) delete mode 100644 .eslintrc.json delete mode 100644 .prettierrc.json create mode 100644 biome.json diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index bd68648..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "env": { - "es2021": true, - "node": true - }, - "extends": ["airbnb-base"], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": 12, - "sourceType": "module" - }, - "plugins": ["@typescript-eslint", "prettier"], - "rules": { - "prettier/prettier": "off", - "quotes": "off", - "no-unused-vars": "warn", - "arrow-parens": "off", - "import/no-unresolved": "off", - "operator-linebreak": "off", - "import/prefer-default-export": "off", - "import/extensions": "off", - "implicit-arrow-linebreak": "off", - "function-paren-newline": "off", - "prefer-destructuring": "off" - } -} diff --git a/.prettierrc.json b/.prettierrc.json deleted file mode 100644 index b526685..0000000 --- a/.prettierrc.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "semi": true, - "singleQuote": true, - "arrowParens": "avoid", - "trailingComma": "all", - "endOfLine": "lf", - "bracketSpacing": true, - "bracketSameLine": false, - "tabWidth": 2 -} diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..442a3d7 --- /dev/null +++ b/biome.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineEnding": "lf" + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "trailingCommas": "all", + "semicolons": "always", + "arrowParentheses": "asNeeded", + "bracketSpacing": true, + "bracketSameLine": false + } + } +} diff --git a/package-lock.json b/package-lock.json index a1d117a..70e9a29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,21 +13,14 @@ "handlebars": "^4.7.7" }, "devDependencies": { + "@biomejs/biome": "^1.9.4", "@semantic-release/changelog": "^6.0.2", "@semantic-release/git": "^10.0.1", "@types/jest": "^30.0.0", "@types/node": "18.x", "@types/vscode": "^1.74.0", - "@typescript-eslint/eslint-plugin": "^5.48.0", - "@typescript-eslint/parser": "^5.48.0", - "eslint": "^8.31.0", - "eslint-config-airbnb-base": "^15.0.0", - "eslint-config-prettier": "^8.6.0", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-prettier": "^4.2.1", "git-commit-msg-linter": "^4.5.0", "jest": "^30.1.1", - "prettier": "^2.8.1", "semantic-release": "^19.0.5", "ts-jest": "^29.4.1", "ts-loader": "^9.4.2", @@ -573,6 +566,170 @@ "dev": true, "license": "MIT" }, + "node_modules/@biomejs/biome": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz", + "integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==", + "dev": true, + "hasInstallScript": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "1.9.4", + "@biomejs/cli-darwin-x64": "1.9.4", + "@biomejs/cli-linux-arm64": "1.9.4", + "@biomejs/cli-linux-arm64-musl": "1.9.4", + "@biomejs/cli-linux-x64": "1.9.4", + "@biomejs/cli-linux-x64-musl": "1.9.4", + "@biomejs/cli-win32-arm64": "1.9.4", + "@biomejs/cli-win32-x64": "1.9.4" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz", + "integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz", + "integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz", + "integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz", + "integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz", + "integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz", + "integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz", + "integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz", + "integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -652,68 +809,6 @@ "license": "0BSD", "optional": true }, - "node_modules/@eslint/eslintrc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", - "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.19.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": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", - "deprecated": "Use @eslint/config-array instead", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1854,13 +1949,6 @@ "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": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4= sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/minimist": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", @@ -1896,13 +1984,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -1934,202 +2015,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.0.tgz", - "integrity": "sha512-SVLafp0NXpoJY7ut6VFVUU9I+YeFsDzeQwtK0WZ+xbRN3mtxJ08je+6Oi2N89qDn087COdO0u3blKZNv9VetRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "5.48.0", - "@typescript-eslint/type-utils": "5.48.0", - "@typescript-eslint/utils": "5.48.0", - "debug": "^4.3.4", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "regexpp": "^3.2.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.0.tgz", - "integrity": "sha512-1mxNA8qfgxX8kBvRDIHEzrRGrKHQfQlbW6iHyfHYS0Q4X1af+S6mkLNtgCOsGVl8+/LUPrqdHMssAemkrQ01qg==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "dependencies": { - "@typescript-eslint/scope-manager": "5.48.0", - "@typescript-eslint/types": "5.48.0", - "@typescript-eslint/typescript-estree": "5.48.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.0.tgz", - "integrity": "sha512-0AA4LviDtVtZqlyUQnZMVHydDATpD9SAX/RC5qh6cBd3xmyWvmXYF+WT1oOmxkeMnWDlUVTwdODeucUnjz3gow==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "5.48.0", - "@typescript-eslint/visitor-keys": "5.48.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.48.0.tgz", - "integrity": "sha512-vbtPO5sJyFjtHkGlGK4Sthmta0Bbls4Onv0bEqOGm7hP9h8UpRsHJwsrCiWtCUndTRNQO/qe6Ijz9rnT/DB+7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "5.48.0", - "@typescript-eslint/utils": "5.48.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.0.tgz", - "integrity": "sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.0.tgz", - "integrity": "sha512-7pjd94vvIjI1zTz6aq/5wwE/YrfIyEPLtGJmRfyNR9NYIW+rOvzzUv3Cmq2hRKpvt6e9vpvPUQ7puzX7VSmsEw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "5.48.0", - "@typescript-eslint/visitor-keys": "5.48.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.48.0.tgz", - "integrity": "sha512-x2jrMcPaMfsHRRIkL+x96++xdzvrdBCnYRd5QiW5Wgo1OB4kDYPbC1XjWP/TNqlfK93K/lUL92erq5zPLgFScQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.48.0", - "@typescript-eslint/types": "5.48.0", - "@typescript-eslint/typescript-estree": "5.48.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0", - "semver": "^7.3.7" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz", - "integrity": "sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "5.48.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", @@ -2653,16 +2538,6 @@ "acorn": "^8" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -2815,54 +2690,16 @@ "dev": true, "license": "MIT" }, - "node_modules/array-includes": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", - "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", - "get-intrinsic": "^1.1.1", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/array.prototype.flat": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", - "integrity": "sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", @@ -3586,13 +3423,6 @@ "dev": true, "license": "MIT" }, - "node_modules/confusing-browser-globals": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz", - "integrity": "sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA==", - "dev": true, - "license": "MIT" - }, "node_modules/conventional-changelog-angular": { "version": "5.0.13", "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz", @@ -3868,13 +3698,6 @@ "node": ">=4.0.0" } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -3885,19 +3708,6 @@ "node": ">=0.10.0" } }, - "node_modules/define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "object-keys": "^1.0.12" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/del": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", @@ -3998,19 +3808,6 @@ "node": ">=8" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -4202,41 +3999,6 @@ "is-arrayish": "^0.2.1" } }, - "node_modules/es-abstract": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", - "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-symbols": "^1.0.2", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.1", - "is-string": "^1.0.7", - "is-weakref": "^1.0.1", - "object-inspect": "^1.11.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/es-module-lexer": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", @@ -4244,24 +4006,6 @@ "dev": true, "license": "MIT" }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/es6-promise": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", @@ -4289,251 +4033,6 @@ "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": "8.31.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.31.0.tgz", - "integrity": "sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@eslint/eslintrc": "^1.4.1", - "@humanwhocodes/config-array": "^0.11.8", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-airbnb-base": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", - "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", - "dev": true, - "license": "MIT", - "dependencies": { - "confusing-browser-globals": "^1.0.10", - "object.assign": "^4.1.2", - "object.entries": "^1.1.5", - "semver": "^6.3.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "peerDependencies": { - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.2" - } - }, - "node_modules/eslint-config-airbnb-base/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-config-prettier": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.6.0.tgz", - "integrity": "sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA==", - "dev": true, - "license": "MIT", - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", - "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.2.7", - "resolve": "^1.20.0" - } - }, - "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.7.4", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", - "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", - "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.26.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", - "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "array-includes": "^3.1.4", - "array.prototype.flat": "^1.2.5", - "debug": "^2.6.9", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.7.3", - "has": "^1.0.3", - "is-core-module": "^2.8.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.values": "^1.1.5", - "resolve": "^1.22.0", - "tsconfig-paths": "^3.14.1" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/eslint-plugin-import/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/eslint-plugin-import/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint-plugin-prettier": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", - "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prettier-linter-helpers": "^1.0.0" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "eslint": ">=7.28.0", - "prettier": ">=2.0.0" - }, - "peerDependenciesMeta": { - "eslint-config-prettier": { - "optional": true - } - } - }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -4548,87 +4047,6 @@ "node": ">=8.0.0" } }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/espree": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", - "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -4643,29 +4061,6 @@ "node": ">=4" } }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -4699,16 +4094,6 @@ "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/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -4788,13 +4173,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/fast-glob": { "version": "3.2.12", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", @@ -4832,13 +4210,6 @@ "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": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, "node_modules/fastest-levenshtein": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", @@ -4902,19 +4273,6 @@ "node": ">=0.8.0" } }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -4928,23 +4286,6 @@ "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/find-versions": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-4.0.0.tgz", @@ -4961,27 +4302,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", - "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", - "dev": true, - "license": "ISC" - }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -5159,23 +4479,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/git-commit-msg-linter": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/git-commit-msg-linter/-/git-commit-msg-linter-4.5.0.tgz", @@ -5350,19 +4653,6 @@ "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/glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", @@ -5396,22 +4686,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/globals": { - "version": "13.19.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", - "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -5440,13 +4714,6 @@ "dev": true, "license": "ISC" }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true, - "license": "MIT" - }, "node_modules/growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", @@ -5501,16 +4768,6 @@ "node": ">= 0.4.0" } }, - "node_modules/has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", - "dev": true, - "license": "MIT", - "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", @@ -5534,22 +4791,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", @@ -5796,21 +5037,6 @@ "dev": true, "license": "ISC" }, - "node_modules/internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/interpret": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", @@ -5845,49 +5071,6 @@ "dev": true, "license": "MIT" }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-core-module": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", @@ -5901,22 +5084,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -5960,19 +5127,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -5983,22 +5137,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-number-object": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", - "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", @@ -6049,33 +5187,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", - "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -6089,62 +5200,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-text-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", - "integrity": "sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "text-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-weakref": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz", - "integrity": "sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ==", + "integrity": "sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.0" + "text-extensions": "^1.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=0.10.0" } }, "node_modules/isarray": { @@ -6879,17 +5945,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/js-sdsl": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", - "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6897,19 +5952,6 @@ "dev": true, "license": "MIT" }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -6944,13 +5986,6 @@ "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": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -7053,20 +6088,6 @@ ], "license": "Public Domain" }, - "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/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -7134,22 +6155,6 @@ "node": ">=6.11.5" } }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -7199,13 +6204,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, "node_modules/lodash.uniqby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", @@ -7764,13 +6762,6 @@ "dev": true, "license": "MIT" }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true, - "license": "MIT" - }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -10496,68 +9487,6 @@ "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.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.entries": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", - "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.values": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", - "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -10584,24 +9513,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "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.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/p-each-series": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", @@ -10654,22 +9565,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-map": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", @@ -11101,46 +9996,6 @@ "node": ">=10" } }, - "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": "2.8.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.1.tgz", - "integrity": "sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "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/pretty-format": { "version": "30.0.5", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz", @@ -11522,19 +10377,6 @@ "esprima": "~4.0.0" } }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, "node_modules/registry-auth-token": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.2.tgz", @@ -12367,34 +11209,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -12792,13 +11606,6 @@ "node": ">=0.10" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -12987,65 +11794,6 @@ "webpack": "^5.0.0" } }, - "node_modules/tsconfig-paths": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", - "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/tsconfig-paths/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true, - "license": "0BSD" - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, "node_modules/tunnel": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", @@ -13069,19 +11817,6 @@ "node": "*" } }, - "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/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -13092,19 +11827,6 @@ "node": ">=4" } }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/typed-rest-client": { "version": "1.8.9", "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.9.tgz", @@ -13152,22 +11874,6 @@ "node": ">=0.8.0" } }, - "node_modules/unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/underscore": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", @@ -13821,23 +12527,6 @@ "node": ">= 8" } }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/wildcard": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", @@ -13845,16 +12534,6 @@ "dev": true, "license": "MIT" }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", diff --git a/package.json b/package.json index 616cabf..a6da8fa 100644 --- a/package.json +++ b/package.json @@ -52,8 +52,8 @@ "watch": "webpack --watch", "package": "webpack --mode production --devtool hidden-source-map", "build": "vsce package", - "lint": "eslint src --fix --ext ts src && prettier --write src", - "lint:check": "eslint src --ext ts src && prettier --check src", + "lint:check": "biome check --error-on-warnings .", + "lint:fix": "biome check --error-on-warnings . --write", "test": "jest", "test:watch": "jest --watch", "test:coverage": "jest --coverage", @@ -74,16 +74,9 @@ "@types/jest": "^30.0.0", "@types/node": "18.x", "@types/vscode": "^1.74.0", - "@typescript-eslint/eslint-plugin": "^5.48.0", - "@typescript-eslint/parser": "^5.48.0", - "eslint": "^8.31.0", - "eslint-config-airbnb-base": "^15.0.0", - "eslint-config-prettier": "^8.6.0", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-prettier": "^4.2.1", + "@biomejs/biome": "^1.9.4", "git-commit-msg-linter": "^4.5.0", "jest": "^30.1.1", - "prettier": "^2.8.1", "semantic-release": "^19.0.5", "ts-jest": "^29.4.1", "ts-loader": "^9.4.2", From 6b8f36ba159bcd6ebc6d9a3816a692edc9f56391 Mon Sep 17 00:00:00 2001 From: Douglas Gomes Date: Fri, 17 Apr 2026 17:00:08 -0300 Subject: [PATCH 03/17] chore: update GitHub workflows for improved concurrency and Node.js version - Added concurrency settings to both pull_request.yml and push_main.yml to cancel in-progress runs. - Updated Node.js version from 16.x to 24.x in both workflows. - Refactored job names for clarity: 'check-code' to 'lint' and added 'test' and 'build' jobs in pull_request.yml. - Upgraded actions/checkout and actions/setup-node to version 4 for better compatibility. --- .github/workflows/pull_request.yml | 46 +++++++++++++++++++++++++----- .github/workflows/push_main.yml | 31 +++++++++++--------- 2 files changed, 56 insertions(+), 21 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index b06870c..f23f371 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -4,19 +4,21 @@ on: pull_request: branches: [main] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: - check-code: + lint: + name: Lint runs-on: ubuntu-latest steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.9.1 - - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - - uses: actions/setup-node@v1 + - uses: actions/setup-node@v4 with: - node-version: "16.x" + node-version: "24.x" cache: "npm" - name: Install Dependencies @@ -25,8 +27,38 @@ jobs: - name: Checking code style run: npm run lint:check + test: + name: Test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "24.x" + cache: "npm" + + - name: Install Dependencies + run: npm ci + - name: Running tests run: npm test + build: + name: Build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "24.x" + cache: "npm" + + - name: Install Dependencies + run: npm ci + - name: Building run: npm run build diff --git a/.github/workflows/push_main.yml b/.github/workflows/push_main.yml index 8aff86a..17e72b0 100644 --- a/.github/workflows/push_main.yml +++ b/.github/workflows/push_main.yml @@ -4,19 +4,20 @@ on: push: branches: [main] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: publish: runs-on: ubuntu-latest steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.9.1 - - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - - uses: actions/setup-node@v1 + - uses: actions/setup-node@v4 with: - node-version: "16.x" + node-version: "24.x" cache: "npm" - name: Install Dependencies @@ -28,22 +29,24 @@ jobs: - name: Running tests run: npm test - - name: Building - run: npm run build - - name: Create Release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} run: npm run semantic-release + - name: Building + run: npm run build + - name: Publish to Visual Studio Marketplace - uses: HaaLeo/publish-vscode-extension@v0 + id: publishVSMarketplace + uses: HaaLeo/publish-vscode-extension@v1 with: pat: ${{ secrets.VS_MARKETPLACE_TOKEN }} registryUrl: https://marketplace.visualstudio.com - # - name: Publish to Open VSX Registry - # uses: HaaLeo/publish-vscode-extension@v0 - # with: - # pat: ${{ secrets.OPEN_VSX_TOKEN }} + - name: Publish to Open VSX Registry + uses: HaaLeo/publish-vscode-extension@v1 + with: + pat: ${{ secrets.OPEN_VSX_TOKEN }} + extensionFile: ${{ steps.publishVSMarketplace.outputs.vsixPath }} From 1191bb9d938411934c471cd70af16fb3485bc084 Mon Sep 17 00:00:00 2001 From: Douglas Gomes Date: Fri, 17 Apr 2026 17:02:53 -0300 Subject: [PATCH 04/17] chore: update project configuration and dependencies - Upgraded Biome schema version in biome.json and adjusted import organization settings. - Refactored jest.config.js for improved readability and updated module paths. - Updated package-lock.json and package.json to reflect new versions of dependencies, including @biomejs/biome and semantic-release. - Enhanced tsconfig.json with additional compiler options and updated target to ES2022. - Modified webpack.config.js to use 'node:path' and improved output library configuration. - Refined VSCode settings and tasks for better development experience. - Updated helper functions to use 'node:fs' and 'node:path' for consistency. - Improved error handling and code structure in createComponent and updateComponent commands. --- .vscode/launch.json | 50 +- .vscode/settings.json | 42 +- .vscode/tasks.json | 57 +- biome.json | 7 +- jest.config.js | 42 +- package-lock.json | 13798 +++++++++------- package.json | 36 +- src/commands/createComponent.ts | 167 +- src/commands/updateComponent.ts | 156 +- src/constants/index.ts | 44 +- src/errors/CodebotError.ts | 249 +- src/errors/index.ts | 2 +- src/helpers/checkExistsFile.ts | 2 +- src/helpers/checkExistsFolder.ts | 2 +- src/helpers/createFile.ts | 2 +- src/helpers/createFolder.ts | 2 +- src/helpers/getConfigurationFile.ts | 16 +- src/helpers/getFilesByFolder.ts | 2 +- src/helpers/getWorkspaceFolder.ts | 5 +- src/helpers/index.ts | 18 +- src/helpers/readFile.ts | 2 +- src/interfaces/index.ts | 126 +- src/managers/ConfigurationManager.ts | 416 +- src/managers/FileSystemManager.ts | 470 +- src/managers/ProjectDetector.ts | 332 +- src/managers/TemplateManager.ts | 1249 +- src/managers/index.ts | 8 +- src/test/__mocks__/vscode.js | 102 +- src/test/commands/updateComponent.test.ts | 562 +- src/test/fixtures/index.ts | 118 +- src/test/managers/FileSystemManager.test.ts | 726 +- src/test/managers/TemplateManager.test.ts | 558 +- src/test/setup.ts | 24 +- .../unit/commands/updateComponent.test.ts | 552 +- .../managers/ConfigurationManager.test.ts | 515 +- .../unit/managers/ProjectDetector.test.ts | 425 +- .../unit/managers/TemplateManager.test.ts | 1391 +- .../managers/TemplateManagerNaming.test.ts | 405 +- .../utils/PathResolver.integration.test.ts | 533 +- src/test/unit/utils/PathResolver.test.ts | 685 +- src/test/utils/DirectoryProcessor.test.ts | 474 +- src/types/index.ts | 10 +- src/utils/DirectoryProcessor.ts | 241 +- src/utils/PathResolver.ts | 609 +- src/utils/index.ts | 4 +- src/utils/validation.ts | 69 +- tsconfig.json | 32 +- webpack.config.js | 6 +- 48 files changed, 14040 insertions(+), 11303 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 3bb6aee..367a9ae 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -3,32 +3,26 @@ // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 { - "version": "0.2.0", - "configurations": [ - { - "name": "Run Extension", - "type": "extensionHost", - "request": "launch", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}" - ], - "outFiles": [ - "${workspaceFolder}/dist/**/*.js" - ], - "preLaunchTask": "${defaultBuildTask}" - }, - { - "name": "Extension Tests", - "type": "extensionHost", - "request": "launch", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}", - "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" - ], - "outFiles": [ - "${workspaceFolder}/out/test/**/*.js" - ], - "preLaunchTask": "npm: test-watch" - } - ] + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "outFiles": ["${workspaceFolder}/dist/**/*.js"], + "preLaunchTask": "${defaultBuildTask}" + }, + { + "name": "Extension Tests", + "type": "extensionHost", + "request": "launch", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}", + "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" + ], + "outFiles": ["${workspaceFolder}/out/test/**/*.js"], + "preLaunchTask": "npm: test-watch" + } + ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index fd168a5..3852689 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,20 +1,34 @@ // Place your settings in this file to overwrite default and user settings. { - "files.exclude": { - "out": false, // set this to true to hide the "out" folder with the compiled JS files - "dist": false // set this to true to hide the "dist" folder with the compiled JS files + "editor.tabSize": 2, + "editor.rulers": [120, 150], + "editor.formatOnSave": true, + "editor.formatOnPaste": true, + "editor.defaultFormatter": "biomejs.biome", + "editor.codeActionsOnSave": { + "source.fixAll.biome": "explicit", + "source.organizeImports.biome": "explicit" }, - "search.exclude": { - "out": true, // set this to false to include "out" folder in search results - "dist": true // set this to false to include "dist" folder in search results + "[typescript]": { + "editor.defaultFormatter": "biomejs.biome" }, - // Turn off tsc task auto detection since we have the necessary tasks as npm scripts + "[typescriptreact]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[javascript]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[javascriptreact]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[json]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[jsonc]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "typescript.tsc.autoDetect": "off", - "cSpell.words": [ - "codebot", - "douglasgomes", - "styfle", - "vsix" - ], - "kiroAgent.configureMCP": "Disabled" + + "cSpell.words": ["codebot", "douglasgomes", "styfle", "vsix"] } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 136cbbc..d030878 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,33 +1,30 @@ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format { - "version": "2.0.0", - "tasks": [ - { - "type": "npm", - "script": "watch", - "problemMatcher": [ - "$ts-webpack-watch", - "$tslint-webpack-watch" - ], - "isBackground": true, - "presentation": { - "reveal": "never" - }, - "group": { - "kind": "build", - "isDefault": true - } - }, - { - "type": "npm", - "script": "test-watch", - "problemMatcher": "$tsc-watch", - "isBackground": true, - "presentation": { - "reveal": "never" - }, - "group": "build" - } - ] -} \ No newline at end of file + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "watch", + "problemMatcher": ["$ts-webpack-watch", "$tslint-webpack-watch"], + "isBackground": true, + "presentation": { + "reveal": "never" + }, + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "type": "npm", + "script": "test-watch", + "problemMatcher": "$tsc-watch", + "isBackground": true, + "presentation": { + "reveal": "never" + }, + "group": "build" + } + ] +} diff --git a/biome.json b/biome.json index 442a3d7..26e8ab1 100644 --- a/biome.json +++ b/biome.json @@ -1,8 +1,9 @@ { - "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", - "organizeImports": { - "enabled": true + "$schema": "https://biomejs.dev/schemas/2.4.12/schema.json", + "files": { + "includes": ["**", "!dist", "!out", "!node_modules", "!coverage"] }, + "assist": { "actions": { "source": { "organizeImports": "on" } } }, "linter": { "enabled": true, "rules": { diff --git a/jest.config.js b/jest.config.js index 14b7a0b..7f0c935 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,23 +1,19 @@ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - roots: ['/src'], - testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'], - transform: { - '^.+\\.ts$': 'ts-jest', - }, - collectCoverageFrom: [ - 'src/**/*.ts', - '!src/**/*.d.ts', - '!src/test/**/*', - ], - coverageDirectory: 'coverage', - coverageReporters: ['text', 'lcov', 'html'], - moduleNameMapper: { - '^vscode$': '/src/test/__mocks__/vscode.js' - }, - setupFilesAfterEnv: ['/src/test/setup.ts'], - maxWorkers: 1, - forceExit: true, - detectOpenHandles: true -}; \ No newline at end of file +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + roots: ['/src'], + testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'], + transform: { + '^.+\\.ts$': 'ts-jest', + }, + collectCoverageFrom: ['src/**/*.ts', '!src/**/*.d.ts', '!src/test/**/*'], + coverageDirectory: 'coverage', + coverageReporters: ['text', 'lcov', 'html'], + moduleNameMapper: { + '^vscode$': '/src/test/__mocks__/vscode.js', + }, + setupFilesAfterEnv: ['/src/test/setup.ts'], + maxWorkers: 1, + forceExit: true, + detectOpenHandles: true, +}; diff --git a/package-lock.json b/package-lock.json index 70e9a29..f0e597f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,32 +9,79 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "fs": "^0.0.2", - "handlebars": "^4.7.7" + "handlebars": "^4.7.9" }, "devDependencies": { - "@biomejs/biome": "^1.9.4", - "@semantic-release/changelog": "^6.0.2", + "@biomejs/biome": "^2.4.12", + "@semantic-release/changelog": "^6.0.3", "@semantic-release/git": "^10.0.1", "@types/jest": "^30.0.0", - "@types/node": "18.x", - "@types/vscode": "^1.74.0", - "git-commit-msg-linter": "^4.5.0", - "jest": "^30.1.1", - "semantic-release": "^19.0.5", - "ts-jest": "^29.4.1", - "ts-loader": "^9.4.2", - "typescript": "^4.9.4", - "vsce": "^2.15.0", - "vscode": "^1.1.37", - "webpack": "^5.75.0", - "webpack-cli": "^5.0.1" + "@types/node": "24.x", + "@types/vscode": "^1.100.0", + "@vscode/vsce": "^3.9.0", + "git-commit-msg-linter": "^5.0.8", + "jest": "^30.3.0", + "semantic-release": "^25.0.3", + "ts-jest": "^29.4.9", + "ts-loader": "^9.5.7", + "typescript": "^5.8.2", + "webpack": "^5.106.2", + "webpack-cli": "^7.0.2" }, "engines": { "node": ">=24.12.0", - "vscode": "^1.74.0" + "vscode": "^1.100.0" } }, + "node_modules/@actions/core": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-3.0.0.tgz", + "integrity": "sha512-zYt6cz+ivnTmiT/ksRVriMBOiuoUpDCJJlZ5KPl2/FRdvwU3f7MPh9qftvbkXJThragzUZieit2nyHUyw53Seg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@actions/exec": "^3.0.0", + "@actions/http-client": "^4.0.0" + } + }, + "node_modules/@actions/exec": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-3.0.0.tgz", + "integrity": "sha512-6xH/puSoNBXb72VPlZVm7vQ+svQpFyA96qdDBvhB8eNZOE8LtPf9L4oAsfzK/crCL8YZ+19fKYVnM63Sl+Xzlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@actions/io": "^3.0.2" + } + }, + "node_modules/@actions/http-client": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-4.0.0.tgz", + "integrity": "sha512-QuwPsgVMsD6qaPD57GLZi9sqzAZCtiJT8kVBCDpLtxhL5MydQ4gS+DrejtZZPdIYyB1e95uCK9Luyds7ybHI3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tunnel": "^0.0.6", + "undici": "^6.23.0" + } + }, + "node_modules/@actions/http-client/node_modules/undici": { + "version": "6.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.25.0.tgz", + "integrity": "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/@actions/io": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-3.0.2.tgz", + "integrity": "sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw==", + "dev": true, + "license": "MIT" + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -49,6 +96,192 @@ "node": ">=6.0.0" } }, + "node_modules/@azu/format-text": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@azu/format-text/-/format-text-1.0.2.tgz", + "integrity": "sha512-Swi4N7Edy1Eqq82GxgEECXSSLyn6GOb5htRFPzBDdUkECGXtlf12ynO5oJSpWKPwCaUssOu7NfhDcCWpIC6Ywg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@azu/style-format": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@azu/style-format/-/style-format-1.0.1.tgz", + "integrity": "sha512-AHcTojlNBdD/3/KxIKlg8sxIWHfOtQszLvOpagLTO+bjC3u7SAszu1lf//u7JJC50aUSH+BVWDD/KvaA6Gfn5g==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "@azu/format-text": "^1.0.1" + } + }, + "node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-auth": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.10.1.tgz", + "integrity": "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-util": "^1.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-client": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.1.tgz", + "integrity": "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-rest-pipeline": "^1.22.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-rest-pipeline": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.23.0.tgz", + "integrity": "sha512-Evs1INHo+jUjwHi1T6SG6Ua/LHOQBCLuKEEE6efIpt4ZOoNonaT1kP32GoOcdNDbfqsD2445CPri3MubBy5DEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "@typespec/ts-http-runtime": "^0.3.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-tracing": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.3.1.tgz", + "integrity": "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-util": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.13.1.tgz", + "integrity": "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/identity": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.13.1.tgz", + "integrity": "sha512-5C/2WD5Vb1lHnZS16dNQRPMjN6oV/Upba+C9nBIs15PmOi6A3ZGs4Lr2u60zw4S04gi+u3cEXiqTVP7M4Pz3kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.9.0", + "@azure/core-client": "^1.9.2", + "@azure/core-rest-pipeline": "^1.17.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.11.0", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^5.5.0", + "@azure/msal-node": "^5.1.0", + "open": "^10.1.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/logger": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.3.0.tgz", + "integrity": "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/msal-browser": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.7.0.tgz", + "integrity": "sha512-uYbJ0YarxkVGWEq814BysJry/IPvpDNkVKmc2bMZp4G+igUQkJ5nlFirycwPGUeA9ICLQqCxqExCA1Z1E07bPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/msal-common": "16.5.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-common": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.5.0.tgz", + "integrity": "sha512-i3eS/5pmxDbIU/mLMENs88Qg3k6XxqJytJy6PpB7L1tCBjdXHJDadCD3Hu1TyTooe7iQo7CYqbocgL/l/8u90g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-node": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-5.1.3.tgz", + "integrity": "sha512-LqT8mRZpEils9zGR9eW+Ljqifh2aMA99UF/X0jxIKDYZeHr6onlHwhVP4xHCeLhh55BI63JCbdf1iWJbMh1mPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/msal-common": "16.5.0", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -203,9 +436,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "dev": true, "license": "MIT", "engines": { @@ -370,13 +603,13 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -496,13 +729,13 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -567,11 +800,10 @@ "license": "MIT" }, "node_modules/@biomejs/biome": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz", - "integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==", + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.12.tgz", + "integrity": "sha512-Rro7adQl3NLq/zJCIL98eElXKI8eEiBtoeu5TbXF/U3qbjuSc7Jb5rjUbeHHcquDWeSf3HnGP7XI5qGrlRk/pA==", "dev": true, - "hasInstallScript": true, "license": "MIT OR Apache-2.0", "bin": { "biome": "bin/biome" @@ -584,20 +816,20 @@ "url": "https://opencollective.com/biome" }, "optionalDependencies": { - "@biomejs/cli-darwin-arm64": "1.9.4", - "@biomejs/cli-darwin-x64": "1.9.4", - "@biomejs/cli-linux-arm64": "1.9.4", - "@biomejs/cli-linux-arm64-musl": "1.9.4", - "@biomejs/cli-linux-x64": "1.9.4", - "@biomejs/cli-linux-x64-musl": "1.9.4", - "@biomejs/cli-win32-arm64": "1.9.4", - "@biomejs/cli-win32-x64": "1.9.4" + "@biomejs/cli-darwin-arm64": "2.4.12", + "@biomejs/cli-darwin-x64": "2.4.12", + "@biomejs/cli-linux-arm64": "2.4.12", + "@biomejs/cli-linux-arm64-musl": "2.4.12", + "@biomejs/cli-linux-x64": "2.4.12", + "@biomejs/cli-linux-x64-musl": "2.4.12", + "@biomejs/cli-win32-arm64": "2.4.12", + "@biomejs/cli-win32-x64": "2.4.12" } }, "node_modules/@biomejs/cli-darwin-arm64": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz", - "integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==", + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.12.tgz", + "integrity": "sha512-BnMU4Pc3ciEVteVpZ0BK33MLr7X57F5w1dwDLDn+/iy/yTrA4Q/N2yftidFtsA4vrDh0FMXDpacNV/Tl3fbmng==", "cpu": [ "arm64" ], @@ -612,9 +844,9 @@ } }, "node_modules/@biomejs/cli-darwin-x64": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz", - "integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==", + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.12.tgz", + "integrity": "sha512-x9uJ0bI1rJsWICp3VH8w/5PnAVD3A7SqzDpbrfoUQX1QyWrK5jSU4fRLo/wSgGeplCivbxBRKmt5Xq4/nWvq8A==", "cpu": [ "x64" ], @@ -629,9 +861,9 @@ } }, "node_modules/@biomejs/cli-linux-arm64": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz", - "integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==", + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.12.tgz", + "integrity": "sha512-tOwuCuZZtKi1jVzbk/5nXmIsziOB6yqN8c9r9QM0EJYPU6DpQWf11uBOSCfFKKM4H3d9ZoarvlgMfbcuD051Pw==", "cpu": [ "arm64" ], @@ -646,9 +878,9 @@ } }, "node_modules/@biomejs/cli-linux-arm64-musl": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz", - "integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==", + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.12.tgz", + "integrity": "sha512-FhfpkAAlKL6kwvcVap0Hgp4AhZmtd3YImg0kK1jd7C/aSoh4SfsB2f++yG1rU0lr8Y5MCFJrcSkmssiL9Xnnig==", "cpu": [ "arm64" ], @@ -663,9 +895,9 @@ } }, "node_modules/@biomejs/cli-linux-x64": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz", - "integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==", + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.12.tgz", + "integrity": "sha512-8pFeAnLU9QdW9jCIslB/v82bI0lhBmz2ZAKc8pVMFPO0t0wAHsoEkrUQUbMkIorTRIjbqyNZHA3lEXavsPWYSw==", "cpu": [ "x64" ], @@ -680,9 +912,9 @@ } }, "node_modules/@biomejs/cli-linux-x64-musl": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz", - "integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==", + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.12.tgz", + "integrity": "sha512-dwTIgZrGutzhkQCuvHynCkyW6hJxUuyZqKKO0YNfaS2GUoRO+tOvxXZqZB6SkWAOdfZTzwaw8IEdUnIkHKHoew==", "cpu": [ "x64" ], @@ -697,9 +929,9 @@ } }, "node_modules/@biomejs/cli-win32-arm64": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz", - "integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==", + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.12.tgz", + "integrity": "sha512-B0DLnx0vA9ya/3v7XyCaP+/lCpnbWbMOfUFFve+xb5OxyYvdHaS55YsSddr228Y+JAFk58agCuZTsqNiw2a6ig==", "cpu": [ "arm64" ], @@ -714,9 +946,9 @@ } }, "node_modules/@biomejs/cli-win32-x64": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz", - "integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==", + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.12.tgz", + "integrity": "sha512-yMckRzTyZ83hkk8iDFWswqSdU8tvZxspJKnYNh7JZr/zhZNOlzH13k4ecboU6MurKExCe2HUkH75pGI/O2JwGA==", "cpu": [ "x64" ], @@ -742,39 +974,31 @@ } }, "node_modules/@discoveryjs/json-ext": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz", - "integrity": "sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-1.0.0.tgz", + "integrity": "sha512-dDlz3W405VMFO4w5kIP9DOmELBcvFQGmLoKSdIRstBDubKFYwaNHV1NnlzMCQpXQFGWVALmeMORAuiLx18AvZQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=10.0.0" + "node": ">=14.17.0" } }, "node_modules/@emnapi/core": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.5.0.tgz", - "integrity": "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/wasi-threads": "1.1.0", + "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, - "node_modules/@emnapi/core/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, "node_modules/@emnapi/runtime": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", - "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", "dev": true, "license": "MIT", "optional": true, @@ -782,18 +1006,10 @@ "tslib": "^2.4.0" } }, - "node_modules/@emnapi/runtime/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", "dev": true, "license": "MIT", "optional": true, @@ -801,59 +1017,14 @@ "tslib": "^2.4.0" } }, - "node_modules/@emnapi/wasi-threads/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", - "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, + "license": "BlueOak-1.0.0", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": ">=18" } }, "node_modules/@istanbuljs/load-nyc-config": { @@ -883,24 +1054,10 @@ "sprintf-js": "~1.0.2" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "license": "MIT", "dependencies": { @@ -911,58 +1068,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -974,17 +1079,17 @@ } }, "node_modules/@jest/console": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.1.1.tgz", - "integrity": "sha512-f7TGqR1k4GtN5pyFrKmq+ZVndesiwLU33yDpJIGMS9aW+j6hKjue7ljeAdznBsH9kAnxUWe2Y+Y3fLV/FJt3gA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.3.0.tgz", + "integrity": "sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.0.5", + "@jest/types": "30.3.0", "@types/node": "*", "chalk": "^4.1.2", - "jest-message-util": "30.1.0", - "jest-util": "30.0.5", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", "slash": "^3.0.0" }, "engines": { @@ -992,39 +1097,38 @@ } }, "node_modules/@jest/core": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.1.1.tgz", - "integrity": "sha512-3ncU9peZ3D2VdgRkdZtUceTrDgX5yiDRwAFjtxNfU22IiZrpVWlv/FogzDLYSJQptQGfFo3PcHK86a2oG6WUGg==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.3.0.tgz", + "integrity": "sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.1.1", + "@jest/console": "30.3.0", "@jest/pattern": "30.0.1", - "@jest/reporters": "30.1.1", - "@jest/test-result": "30.1.1", - "@jest/transform": "30.1.1", - "@jest/types": "30.0.5", + "@jest/reporters": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "ci-info": "^4.2.0", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", - "jest-changed-files": "30.0.5", - "jest-config": "30.1.1", - "jest-haste-map": "30.1.0", - "jest-message-util": "30.1.0", + "jest-changed-files": "30.3.0", + "jest-config": "30.3.0", + "jest-haste-map": "30.3.0", + "jest-message-util": "30.3.0", "jest-regex-util": "30.0.1", - "jest-resolve": "30.1.0", - "jest-resolve-dependencies": "30.1.1", - "jest-runner": "30.1.1", - "jest-runtime": "30.1.1", - "jest-snapshot": "30.1.1", - "jest-util": "30.0.5", - "jest-validate": "30.1.0", - "jest-watcher": "30.1.1", - "micromatch": "^4.0.8", - "pretty-format": "30.0.5", + "jest-resolve": "30.3.0", + "jest-resolve-dependencies": "30.3.0", + "jest-runner": "30.3.0", + "jest-runtime": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "jest-watcher": "30.3.0", + "pretty-format": "30.3.0", "slash": "^3.0.0" }, "engines": { @@ -1040,9 +1144,9 @@ } }, "node_modules/@jest/diff-sequences": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", - "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz", + "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==", "dev": true, "license": "MIT", "engines": { @@ -1050,39 +1154,39 @@ } }, "node_modules/@jest/environment": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.1.1.tgz", - "integrity": "sha512-yWHbU+3j7ehQE+NRpnxRvHvpUhoohIjMePBbIr8lfe0cWVb0WeTf80DNux1GPJa18CDHiIU5DtksGUfxcDE+Rw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.3.0.tgz", + "integrity": "sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/fake-timers": "30.1.1", - "@jest/types": "30.0.5", + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", - "jest-mock": "30.0.5" + "jest-mock": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.1.1.tgz", - "integrity": "sha512-3vHIHsF+qd3D8FU2c7U5l3rg1fhDwAYcGyHyZAi94YIlTwcJ+boNhRyJf373cl4wxbOX+0Q7dF40RTrTFTSuig==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg==", "dev": true, "license": "MIT", "dependencies": { - "expect": "30.1.1", - "jest-snapshot": "30.1.1" + "expect": "30.3.0", + "jest-snapshot": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.1.1.tgz", - "integrity": "sha512-5YUHr27fpJ64dnvtu+tt11ewATynrHkGYD+uSFgRr8V2eFJis/vEXgToyLwccIwqBihVfz9jwio+Zr1ab1Zihw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.3.0.tgz", + "integrity": "sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==", "dev": true, "license": "MIT", "dependencies": { @@ -1093,18 +1197,18 @@ } }, "node_modules/@jest/fake-timers": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.1.1.tgz", - "integrity": "sha512-fK/25dNgBNYPw3eLi2CRs57g1H04qBAFNMsUY3IRzkfx/m4THe0E1zF+yGQBOMKKc2XQVdc9EYbJ4hEm7/2UtA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.3.0.tgz", + "integrity": "sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.0.5", - "@sinonjs/fake-timers": "^13.0.0", + "@jest/types": "30.3.0", + "@sinonjs/fake-timers": "^15.0.0", "@types/node": "*", - "jest-message-util": "30.1.0", - "jest-mock": "30.0.5", - "jest-util": "30.0.5" + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -1121,16 +1225,16 @@ } }, "node_modules/@jest/globals": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.1.1.tgz", - "integrity": "sha512-NNUUkHT2TU/xztZl6r1UXvJL+zvCwmZsQDmK69fVHHcB9fBtlu3FInnzOve/ZoyKnWY8JXWJNT+Lkmu1+ubXUA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.3.0.tgz", + "integrity": "sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.1.1", - "@jest/expect": "30.1.1", - "@jest/types": "30.0.5", - "jest-mock": "30.0.5" + "@jest/environment": "30.3.0", + "@jest/expect": "30.3.0", + "@jest/types": "30.3.0", + "jest-mock": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -1151,32 +1255,32 @@ } }, "node_modules/@jest/reporters": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.1.1.tgz", - "integrity": "sha512-Hb2Bq80kahOC6Sv2waEaH1rEU6VdFcM6WHaRBWQF9tf30+nJHxhl/Upbgo9+25f0mOgbphxvbwSMjSgy9gW/FA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.3.0.tgz", + "integrity": "sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.1.1", - "@jest/test-result": "30.1.1", - "@jest/transform": "30.1.1", - "@jest/types": "30.0.5", + "@jest/console": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", "@jridgewell/trace-mapping": "^0.3.25", "@types/node": "*", "chalk": "^4.1.2", "collect-v8-coverage": "^1.0.2", "exit-x": "^0.2.2", - "glob": "^10.3.10", + "glob": "^10.5.0", "graceful-fs": "^4.2.11", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "30.1.0", - "jest-util": "30.0.5", - "jest-worker": "30.1.0", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", "slash": "^3.0.0", "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" @@ -1193,27 +1297,189 @@ } } }, - "node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "node_modules/@jest/reporters/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@sinclair/typebox": "^0.34.0" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jest/reporters/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@jest/reporters/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/@jest/reporters/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "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/@jest/reporters/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/@jest/reporters/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@jest/reporters/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/reporters/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/reporters/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@jest/reporters/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/snapshot-utils": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.1.1.tgz", - "integrity": "sha512-TkVBc9wuN22TT8hESRFmjjg/xIMu7z0J3UDYtIRydzCqlLPTB7jK1DDBKdnTUZ4zL3z3rnPpzV6rL1Uzh87sXg==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.3.0.tgz", + "integrity": "sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.0.5", + "@jest/types": "30.3.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "natural-compare": "^1.4.0" @@ -1238,14 +1504,14 @@ } }, "node_modules/@jest/test-result": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.1.1.tgz", - "integrity": "sha512-bMdj7fNu8iZuBPSnbVir5ezvWmVo4jrw7xDE+A33Yb3ENCoiJK9XgOLgal+rJ9XSKjsL7aPUMIo87zhN7I5o2w==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.3.0.tgz", + "integrity": "sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.1.1", - "@jest/types": "30.0.5", + "@jest/console": "30.3.0", + "@jest/types": "30.3.0", "@types/istanbul-lib-coverage": "^2.0.6", "collect-v8-coverage": "^1.0.2" }, @@ -1254,15 +1520,15 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.1.1.tgz", - "integrity": "sha512-yruRdLXSA3HYD/MTNykgJ6VYEacNcXDFRMqKVAwlYegmxICUiT/B++CNuhJnYJzKYks61iYnjVsMwbUqmmAYJg==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.3.0.tgz", + "integrity": "sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.1.1", + "@jest/test-result": "30.3.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.1.0", + "jest-haste-map": "30.3.0", "slash": "^3.0.0" }, "engines": { @@ -1270,24 +1536,23 @@ } }, "node_modules/@jest/transform": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.1.1.tgz", - "integrity": "sha512-PHIA2AbAASBfk6evkNifvmx9lkOSkmvaQoO6VSpuL8+kQqDMHeDoJ7RU3YP1wWAMD7AyQn9UL5iheuFYCC4lqQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.3.0.tgz", + "integrity": "sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", - "@jest/types": "30.0.5", + "@jest/types": "30.3.0", "@jridgewell/trace-mapping": "^0.3.25", - "babel-plugin-istanbul": "^7.0.0", + "babel-plugin-istanbul": "^7.0.1", "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.1.0", + "jest-haste-map": "30.3.0", "jest-regex-util": "30.0.1", - "jest-util": "30.0.5", - "micromatch": "^4.0.8", + "jest-util": "30.3.0", "pirates": "^4.0.7", "slash": "^3.0.0", "write-file-atomic": "^5.0.1" @@ -1297,9 +1562,9 @@ } }, "node_modules/@jest/types": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", - "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", "dev": true, "license": "MIT", "dependencies": { @@ -1336,6 +1601,17 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -1406,175 +1682,161 @@ } }, "node_modules/@octokit/auth-token": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-3.0.2.tgz", - "integrity": "sha512-pq7CwIMV1kmzkFTimdwjAINCXKTajZErLB4wMLYapR2nuB/Jpr66+05wOTZMSCBXP6n4DdDWT2W19Bm17vU69Q==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", + "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", "dev": true, "license": "MIT", - "dependencies": { - "@octokit/types": "^8.0.0" - }, "engines": { - "node": ">= 14" + "node": ">= 20" } }, "node_modules/@octokit/core": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-4.1.0.tgz", - "integrity": "sha512-Czz/59VefU+kKDy+ZfDwtOIYIkFjExOKf+HA92aiTZJ6EfWpFzYQWw0l54ji8bVmyhc+mGaLUbSUmXazG7z5OQ==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz", + "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@octokit/auth-token": "^3.0.0", - "@octokit/graphql": "^5.0.0", - "@octokit/request": "^6.0.0", - "@octokit/request-error": "^3.0.0", - "@octokit/types": "^8.0.0", - "before-after-hook": "^2.2.0", - "universal-user-agent": "^6.0.0" + "@octokit/auth-token": "^6.0.0", + "@octokit/graphql": "^9.0.3", + "@octokit/request": "^10.0.6", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "before-after-hook": "^4.0.0", + "universal-user-agent": "^7.0.0" }, "engines": { - "node": ">= 14" + "node": ">= 20" } }, "node_modules/@octokit/endpoint": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.3.tgz", - "integrity": "sha512-57gRlb28bwTsdNXq+O3JTQ7ERmBTuik9+LelgcLIVfYwf235VHbN9QNo4kXExtp/h8T423cR5iJThKtFYxC7Lw==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.3.tgz", + "integrity": "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^8.0.0", - "is-plain-object": "^5.0.0", - "universal-user-agent": "^6.0.0" + "@octokit/types": "^16.0.0", + "universal-user-agent": "^7.0.2" }, "engines": { - "node": ">= 14" + "node": ">= 20" } }, "node_modules/@octokit/graphql": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-5.0.4.tgz", - "integrity": "sha512-amO1M5QUQgYQo09aStR/XO7KAl13xpigcy/kI8/N1PnZYSS69fgte+xA4+c2DISKqUZfsh0wwjc2FaCt99L41A==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.3.tgz", + "integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/request": "^6.0.0", - "@octokit/types": "^8.0.0", - "universal-user-agent": "^6.0.0" + "@octokit/request": "^10.0.6", + "@octokit/types": "^16.0.0", + "universal-user-agent": "^7.0.0" }, "engines": { - "node": ">= 14" + "node": ">= 20" } }, "node_modules/@octokit/openapi-types": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-14.0.0.tgz", - "integrity": "sha512-HNWisMYlR8VCnNurDU6os2ikx0s0VyEjDYHNS/h4cgb8DeOxQ0n72HyinUtdDVxJhFy3FWLGl0DJhfEWk3P5Iw==", + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", + "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", "dev": true, "license": "MIT" }, "node_modules/@octokit/plugin-paginate-rest": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-5.0.1.tgz", - "integrity": "sha512-7A+rEkS70pH36Z6JivSlR7Zqepz3KVucEFVDnSrgHXzG7WLAzYwcHZbKdfTXHwuTHbkT1vKvz7dHl1+HNf6Qyw==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-14.0.0.tgz", + "integrity": "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^8.0.0" + "@octokit/types": "^16.0.0" }, "engines": { - "node": ">= 14" + "node": ">= 20" }, "peerDependencies": { - "@octokit/core": ">=4" - } - }, - "node_modules/@octokit/plugin-request-log": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", - "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@octokit/core": ">=3" + "@octokit/core": ">=6" } }, - "node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-6.7.0.tgz", - "integrity": "sha512-orxQ0fAHA7IpYhG2flD2AygztPlGYNAdlzYz8yrD8NDgelPfOYoRPROfEyIe035PlxvbYrgkfUZIhSBKju/Cvw==", + "node_modules/@octokit/plugin-retry": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-8.1.0.tgz", + "integrity": "sha512-O1FZgXeiGb2sowEr/hYTr6YunGdSAFWnr2fyW39Ah85H8O33ELASQxcvOFF5LE6Tjekcyu2ms4qAzJVhSaJxTw==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^8.0.0", - "deprecation": "^2.3.1" + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "bottleneck": "^2.15.3" }, "engines": { - "node": ">= 14" + "node": ">= 20" }, "peerDependencies": { - "@octokit/core": ">=3" + "@octokit/core": ">=7" } }, - "node_modules/@octokit/request": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-6.2.2.tgz", - "integrity": "sha512-6VDqgj0HMc2FUX2awIs+sM6OwLgwHvAi4KCK3mT2H2IKRt6oH9d0fej5LluF5mck1lRR/rFWN0YIDSYXYSylbw==", + "node_modules/@octokit/plugin-throttling": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-11.0.3.tgz", + "integrity": "sha512-34eE0RkFCKycLl2D2kq7W+LovheM/ex3AwZCYN8udpi6bxsyjZidb2McXs69hZhLmJlDqTSP8cH+jSRpiaijBg==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/endpoint": "^7.0.0", - "@octokit/request-error": "^3.0.0", - "@octokit/types": "^8.0.0", - "is-plain-object": "^5.0.0", - "node-fetch": "^2.6.7", - "universal-user-agent": "^6.0.0" + "@octokit/types": "^16.0.0", + "bottleneck": "^2.15.3" }, "engines": { - "node": ">= 14" + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": "^7.0.0" } }, - "node_modules/@octokit/request-error": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.2.tgz", - "integrity": "sha512-WMNOFYrSaX8zXWoJg9u/pKgWPo94JXilMLb2VManNOby9EZxrQaBe/QSC4a1TzpAlpxofg2X/jMnCyZgL6y7eg==", + "node_modules/@octokit/request": { + "version": "10.0.8", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.8.tgz", + "integrity": "sha512-SJZNwY9pur9Agf7l87ywFi14W+Hd9Jg6Ifivsd33+/bGUQIjNujdFiXII2/qSlN2ybqUHfp5xpekMEjIBTjlSw==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^8.0.0", - "deprecation": "^2.0.0", - "once": "^1.4.0" + "@octokit/endpoint": "^11.0.3", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "fast-content-type-parse": "^3.0.0", + "json-with-bigint": "^3.5.3", + "universal-user-agent": "^7.0.2" }, "engines": { - "node": ">= 14" + "node": ">= 20" } }, - "node_modules/@octokit/rest": { - "version": "19.0.5", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-19.0.5.tgz", - "integrity": "sha512-+4qdrUFq2lk7Va+Qff3ofREQWGBeoTKNqlJO+FGjFP35ZahP+nBenhZiGdu8USSgmq4Ky3IJ/i4u0xbLqHaeow==", + "node_modules/@octokit/request-error": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", + "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/core": "^4.1.0", - "@octokit/plugin-paginate-rest": "^5.0.0", - "@octokit/plugin-request-log": "^1.0.4", - "@octokit/plugin-rest-endpoint-methods": "^6.7.0" + "@octokit/types": "^16.0.0" }, "engines": { - "node": ">= 14" + "node": ">= 20" } }, "node_modules/@octokit/types": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-8.0.0.tgz", - "integrity": "sha512-65/TPpOJP1i3K4lBJMnWqPUJ6zuOtzhtagDvydAWbEXpbFYA0oMKKyLb95NFZZP0lSh/4b6K+DQlzvYQJQQePg==", + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", + "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^14.0.0" + "@octokit/openapi-types": "^27.0.0" } }, "node_modules/@pkgjs/parseargs": { @@ -1601,400 +1863,1169 @@ "url": "https://opencollective.com/pkgr" } }, - "node_modules/@semantic-release/changelog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@semantic-release/changelog/-/changelog-6.0.2.tgz", - "integrity": "sha512-jHqfTkoPbDEOAgAP18mGP53IxeMwxTISN+GwTRy9uLu58UjARoZU8ScCgWGeO2WPkEsm57H8AkyY02W2ntIlIw==", + "node_modules/@pnpm/config.env-replace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", + "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", "dev": true, "license": "MIT", "dependencies": { - "@semantic-release/error": "^3.0.0", - "aggregate-error": "^3.0.0", - "fs-extra": "^11.0.0", - "lodash": "^4.17.4" + "graceful-fs": "4.2.10" }, "engines": { - "node": ">=14.17" - }, - "peerDependencies": { - "semantic-release": ">=18.0.0" + "node": ">=12.22.0" } }, - "node_modules/@semantic-release/commit-analyzer": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-9.0.2.tgz", - "integrity": "sha512-E+dr6L+xIHZkX4zNMe6Rnwg4YQrWNXK+rNsvwOPpdFppvZO1olE2fIgWhv89TkQErygevbjsZFSIxp+u6w2e5g==", + "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true, + "license": "ISC" + }, + "node_modules/@pnpm/npm-conf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-3.0.2.tgz", + "integrity": "sha512-h104Kh26rR8tm+a3Qkc5S4VLYint3FE48as7+/5oCEcKR2idC/pF1G6AhIXKI+eHPJa/3J9i5z0Al47IeGHPkA==", "dev": true, "license": "MIT", "dependencies": { - "conventional-changelog-angular": "^5.0.0", - "conventional-commits-filter": "^2.0.0", - "conventional-commits-parser": "^3.2.3", - "debug": "^4.0.0", - "import-from": "^4.0.0", - "lodash": "^4.17.4", - "micromatch": "^4.0.2" + "@pnpm/config.env-replace": "^1.1.0", + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" }, "engines": { - "node": ">=14.17" - }, - "peerDependencies": { - "semantic-release": ">=18.0.0-beta.1" + "node": ">=12" } }, - "node_modules/@semantic-release/error": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-3.0.0.tgz", - "integrity": "sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw==", + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@secretlint/config-creator": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/config-creator/-/config-creator-10.2.2.tgz", + "integrity": "sha512-BynOBe7Hn3LJjb3CqCHZjeNB09s/vgf0baBaHVw67w7gHF0d25c3ZsZ5+vv8TgwSchRdUCRrbbcq5i2B1fJ2QQ==", "dev": true, "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.2.2" + }, "engines": { - "node": ">=14.17" + "node": ">=20.0.0" } }, - "node_modules/@semantic-release/git": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@semantic-release/git/-/git-10.0.1.tgz", - "integrity": "sha512-eWrx5KguUcU2wUPaO6sfvZI0wPafUKAMNC18aXY4EnNcrZL86dEmpNVnC9uMpGZkmZJ9EfCVJBQx4pV4EMGT1w==", + "node_modules/@secretlint/config-loader": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/config-loader/-/config-loader-10.2.2.tgz", + "integrity": "sha512-ndjjQNgLg4DIcMJp4iaRD6xb9ijWQZVbd9694Ol2IszBIbGPPkwZHzJYKICbTBmh6AH/pLr0CiCaWdGJU7RbpQ==", "dev": true, "license": "MIT", "dependencies": { - "@semantic-release/error": "^3.0.0", - "aggregate-error": "^3.0.0", - "debug": "^4.0.0", - "dir-glob": "^3.0.0", - "execa": "^5.0.0", - "lodash": "^4.17.4", - "micromatch": "^4.0.0", - "p-reduce": "^2.0.0" + "@secretlint/profiler": "^10.2.2", + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", + "ajv": "^8.17.1", + "debug": "^4.4.1", + "rc-config-loader": "^4.1.3" }, "engines": { - "node": ">=14.17" - }, - "peerDependencies": { - "semantic-release": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@semantic-release/github": { - "version": "8.0.7", - "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-8.0.7.tgz", - "integrity": "sha512-VtgicRIKGvmTHwm//iqTh/5NGQwsncOMR5vQK9pMT92Aem7dv37JFKKRuulUsAnUOIlO4G8wH3gPiBAA0iW0ww==", + "node_modules/@secretlint/core": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/core/-/core-10.2.2.tgz", + "integrity": "sha512-6rdwBwLP9+TO3rRjMVW1tX+lQeo5gBbxl1I5F8nh8bgGtKwdlCMhMKsBWzWg1ostxx/tIG7OjZI0/BxsP8bUgw==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/rest": "^19.0.0", - "@semantic-release/error": "^3.0.0", - "aggregate-error": "^3.0.0", - "bottleneck": "^2.18.1", - "debug": "^4.0.0", - "dir-glob": "^3.0.0", - "fs-extra": "^11.0.0", - "globby": "^11.0.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "issue-parser": "^6.0.0", - "lodash": "^4.17.4", - "mime": "^3.0.0", - "p-filter": "^2.0.0", - "p-retry": "^4.0.0", - "url-join": "^4.0.0" + "@secretlint/profiler": "^10.2.2", + "@secretlint/types": "^10.2.2", + "debug": "^4.4.1", + "structured-source": "^4.0.0" }, "engines": { - "node": ">=14.17" - }, - "peerDependencies": { - "semantic-release": ">=18.0.0-beta.1" + "node": ">=20.0.0" } }, - "node_modules/@semantic-release/npm": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-9.0.1.tgz", - "integrity": "sha512-I5nVZklxBzfMFwemhRNbSrkiN/dsH3c7K9+KSk6jUnq0rdLFUuJt7EBsysq4Ir3moajQgFkfEryEHPqiKJj20g==", + "node_modules/@secretlint/formatter": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/formatter/-/formatter-10.2.2.tgz", + "integrity": "sha512-10f/eKV+8YdGKNQmoDUD1QnYL7TzhI2kzyx95vsJKbEa8akzLAR5ZrWIZ3LbcMmBLzxlSQMMccRmi05yDQ5YDA==", "dev": true, "license": "MIT", "dependencies": { - "@semantic-release/error": "^3.0.0", - "aggregate-error": "^3.0.0", - "execa": "^5.0.0", - "fs-extra": "^10.0.0", - "lodash": "^4.17.15", - "nerf-dart": "^1.0.0", - "normalize-url": "^6.0.0", - "npm": "^8.3.0", - "rc": "^1.2.8", - "read-pkg": "^5.0.0", - "registry-auth-token": "^4.0.0", - "semver": "^7.1.2", - "tempy": "^1.0.0" + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", + "@textlint/linter-formatter": "^15.2.0", + "@textlint/module-interop": "^15.2.0", + "@textlint/types": "^15.2.0", + "chalk": "^5.4.1", + "debug": "^4.4.1", + "pluralize": "^8.0.0", + "strip-ansi": "^7.1.0", + "table": "^6.9.0", + "terminal-link": "^4.0.0" }, "engines": { - "node": ">=16 || ^14.17" - }, - "peerDependencies": { - "semantic-release": ">=19.0.0" + "node": ">=20.0.0" } }, - "node_modules/@semantic-release/npm/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "node_modules/@secretlint/formatter/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true, "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, "engines": { - "node": ">=12" + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@semantic-release/release-notes-generator": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-10.0.3.tgz", - "integrity": "sha512-k4x4VhIKneOWoBGHkx0qZogNjCldLPRiAjnIpMnlUh6PtaWXp/T+C9U7/TaNDDtgDa5HMbHl4WlREdxHio6/3w==", + "node_modules/@secretlint/node": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/node/-/node-10.2.2.tgz", + "integrity": "sha512-eZGJQgcg/3WRBwX1bRnss7RmHHK/YlP/l7zOQsrjexYt6l+JJa5YhUmHbuGXS94yW0++3YkEJp0kQGYhiw1DMQ==", "dev": true, "license": "MIT", "dependencies": { - "conventional-changelog-angular": "^5.0.0", - "conventional-changelog-writer": "^5.0.0", - "conventional-commits-filter": "^2.0.0", - "conventional-commits-parser": "^3.2.3", - "debug": "^4.0.0", - "get-stream": "^6.0.0", - "import-from": "^4.0.0", - "into-stream": "^6.0.0", - "lodash": "^4.17.4", - "read-pkg-up": "^7.0.0" + "@secretlint/config-loader": "^10.2.2", + "@secretlint/core": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/profiler": "^10.2.2", + "@secretlint/source-creator": "^10.2.2", + "@secretlint/types": "^10.2.2", + "debug": "^4.4.1", + "p-map": "^7.0.3" }, "engines": { - "node": ">=14.17" - }, - "peerDependencies": { - "semantic-release": ">=18.0.0-beta.1" + "node": ">=20.0.0" } }, - "node_modules/@sinclair/typebox": { - "version": "0.34.40", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.40.tgz", - "integrity": "sha512-gwBNIP8ZAYev/ORDWW0QvxdwPXwxBtLsdsJgSc7eDIRt8ubP+rxUBzPsrwnu16fgEF8Bx4lh/+mvQvJzcTM6Kw==", + "node_modules/@secretlint/profiler": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/profiler/-/profiler-10.2.2.tgz", + "integrity": "sha512-qm9rWfkh/o8OvzMIfY8a5bCmgIniSpltbVlUVl983zDG1bUuQNd1/5lUEeWx5o/WJ99bXxS7yNI4/KIXfHexig==", "dev": true, "license": "MIT" }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "node_modules/@secretlint/resolver": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/resolver/-/resolver-10.2.2.tgz", + "integrity": "sha512-3md0cp12e+Ae5V+crPQYGd6aaO7ahw95s28OlULGyclyyUtf861UoRGS2prnUrKh7MZb23kdDOyGCYb9br5e4w==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT" + }, + "node_modules/@secretlint/secretlint-formatter-sarif": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-formatter-sarif/-/secretlint-formatter-sarif-10.2.2.tgz", + "integrity": "sha512-ojiF9TGRKJJw308DnYBucHxkpNovDNu1XvPh7IfUp0A12gzTtxuWDqdpuVezL7/IP8Ua7mp5/VkDMN9OLp1doQ==", + "dev": true, + "license": "MIT", "dependencies": { - "type-detect": "4.0.8" + "node-sarif-builder": "^3.2.0" } }, - "node_modules/@sinonjs/fake-timers": { - "version": "13.0.5", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", - "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", + "node_modules/@secretlint/secretlint-rule-no-dotenv": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-no-dotenv/-/secretlint-rule-no-dotenv-10.2.2.tgz", + "integrity": "sha512-KJRbIShA9DVc5Va3yArtJ6QDzGjg3PRa1uYp9As4RsyKtKSSZjI64jVca57FZ8gbuk4em0/0Jq+uy6485wxIdg==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "@sinonjs/commons": "^3.0.1" + "@secretlint/types": "^10.2.2" + }, + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "node_modules/@secretlint/secretlint-rule-preset-recommend": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-preset-recommend/-/secretlint-rule-preset-recommend-10.2.2.tgz", + "integrity": "sha512-K3jPqjva8bQndDKJqctnGfwuAxU2n9XNCPtbXVI5JvC7FnQiNg/yWlQPbMUlBXtBoBGFYp08A94m6fvtc9v+zA==", "dev": true, "license": "MIT", "engines": { - "node": ">= 10" + "node": ">=20.0.0" } }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz", - "integrity": "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==", + "node_modules/@secretlint/source-creator": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/source-creator/-/source-creator-10.2.2.tgz", + "integrity": "sha512-h6I87xJfwfUTgQ7irWq7UTdq/Bm1RuQ/fYhA3dtTIAop5BwSFmZyrchph4WcoEvbN460BWKmk4RYSvPElIIvxw==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "tslib": "^2.4.0" + "@secretlint/types": "^10.2.2", + "istextorbinary": "^9.5.0" + }, + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@tybys/wasm-util/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "node_modules/@secretlint/types": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/types/-/types-10.2.2.tgz", + "integrity": "sha512-Nqc90v4lWCXyakD6xNyNACBJNJ0tNCwj2WNk/7ivyacYHxiITVgmLUFXTBOeCdy79iz6HtN9Y31uw/jbLrdOAg==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "node_modules/@semantic-release/changelog": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@semantic-release/changelog/-/changelog-6.0.3.tgz", + "integrity": "sha512-dZuR5qByyfe3Y03TpmCvAxCyTnp7r5XwtHRf/8vD9EAn4ZWbavUX8adMtXYzE86EVh0gyLA7lm5yW4IV30XUag==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.0.0" + "@semantic-release/error": "^3.0.0", + "aggregate-error": "^3.0.0", + "fs-extra": "^11.0.0", + "lodash": "^4.17.4" + }, + "engines": { + "node": ">=14.17" + }, + "peerDependencies": { + "semantic-release": ">=18.0.0" } }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "node_modules/@semantic-release/commit-analyzer": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-13.0.1.tgz", + "integrity": "sha512-wdnBPHKkr9HhNhXOhZD5a2LNl91+hs8CC2vsAVYxtZH3y0dV3wKn+uZSN61rdJQZ8EGxzWB3inWocBHV9+u/CQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" + "conventional-changelog-angular": "^8.0.0", + "conventional-changelog-writer": "^8.0.0", + "conventional-commits-filter": "^5.0.0", + "conventional-commits-parser": "^6.0.0", + "debug": "^4.0.0", + "import-from-esm": "^2.0.0", + "lodash-es": "^4.17.21", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=20.8.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" } }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "node_modules/@semantic-release/error": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-3.0.0.tgz", + "integrity": "sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" + "engines": { + "node": ">=14.17" } }, - "node_modules/@types/eslint": { - "version": "7.28.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.1.tgz", - "integrity": "sha512-XhZKznR3i/W5dXqUhgU9fFdJekufbeBd5DALmkuXoeFcjbQcPk+2cL+WLHf6Q81HWAnM2vrslIHpGVyCAviRwg==", + "node_modules/@semantic-release/git": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/git/-/git-10.0.1.tgz", + "integrity": "sha512-eWrx5KguUcU2wUPaO6sfvZI0wPafUKAMNC18aXY4EnNcrZL86dEmpNVnC9uMpGZkmZJ9EfCVJBQx4pV4EMGT1w==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" + "@semantic-release/error": "^3.0.0", + "aggregate-error": "^3.0.0", + "debug": "^4.0.0", + "dir-glob": "^3.0.0", + "execa": "^5.0.0", + "lodash": "^4.17.4", + "micromatch": "^4.0.0", + "p-reduce": "^2.0.0" + }, + "engines": { + "node": ">=14.17" + }, + "peerDependencies": { + "semantic-release": ">=18.0.0" } }, - "node_modules/@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "node_modules/@semantic-release/github": { + "version": "12.0.6", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-12.0.6.tgz", + "integrity": "sha512-aYYFkwHW3c6YtHwQF0t0+lAjlU+87NFOZuH2CvWFD0Ylivc7MwhZMiHOJ0FMpIgPpCVib/VUAcOwvrW0KnxQtA==", "dev": true, "license": "MIT", "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" + "@octokit/core": "^7.0.0", + "@octokit/plugin-paginate-rest": "^14.0.0", + "@octokit/plugin-retry": "^8.0.0", + "@octokit/plugin-throttling": "^11.0.0", + "@semantic-release/error": "^4.0.0", + "aggregate-error": "^5.0.0", + "debug": "^4.3.4", + "dir-glob": "^3.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "issue-parser": "^7.0.0", + "lodash-es": "^4.17.21", + "mime": "^4.0.0", + "p-filter": "^4.0.0", + "tinyglobby": "^0.2.14", + "undici": "^7.0.0", + "url-join": "^5.0.0" + }, + "engines": { + "node": "^22.14.0 || >= 24.10.0" + }, + "peerDependencies": { + "semantic-release": ">=24.1.0" } }, - "node_modules/@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "node_modules/@semantic-release/github/node_modules/@semantic-release/error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "node_modules/@semantic-release/github/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" + "license": "MIT", + "engines": { + "node": ">= 14" + } }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "node_modules/@semantic-release/github/node_modules/aggregate-error": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", + "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", "dev": true, "license": "MIT", "dependencies": { - "@types/istanbul-lib-coverage": "*" + "clean-stack": "^5.2.0", + "indent-string": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "node_modules/@semantic-release/github/node_modules/clean-stack": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.3.0.tgz", + "integrity": "sha512-9ngPTOhYGQqNVSfeJkYXHmF7AGWp4/nN5D/QqNQs3Dvxd1Kk/WpjHfNujKHYUQ/5CoGyOyFNoWSPk5afzP0QVg==", "dev": true, "license": "MIT", "dependencies": { - "@types/istanbul-lib-report": "*" + "escape-string-regexp": "5.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@types/jest": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", - "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", + "node_modules/@semantic-release/github/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": { - "expect": "^30.0.0", - "pretty-format": "^30.0.0" + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" } }, - "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "node_modules/@semantic-release/github/node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/@types/minimist": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", - "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", + "node_modules/@semantic-release/github/node_modules/mime": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz", + "integrity": "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==", "dev": true, - "license": "MIT" + "funding": [ + "https://github.com/sponsors/broofa" + ], + "license": "MIT", + "bin": { + "mime": "bin/cli.js" + }, + "engines": { + "node": ">=16" + } }, - "node_modules/@types/node": { - "version": "18.11.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", - "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", + "node_modules/@semantic-release/github/node_modules/url-join": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz", + "integrity": "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "node_modules/@semantic-release/npm": { + "version": "13.1.5", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-13.1.5.tgz", + "integrity": "sha512-Hq5UxzoatN3LHiq2rTsWS54nCdqJHlsssGERCo8WlvdfFA9LoN0vO+OuKVSjtNapIc/S8C2LBj206wKLHg62mg==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@actions/core": "^3.0.0", + "@semantic-release/error": "^4.0.0", + "aggregate-error": "^5.0.0", + "env-ci": "^11.2.0", + "execa": "^9.0.0", + "fs-extra": "^11.0.0", + "lodash-es": "^4.17.21", + "nerf-dart": "^1.0.0", + "normalize-url": "^9.0.0", + "npm": "^11.6.2", + "rc": "^1.2.8", + "read-pkg": "^10.0.0", + "registry-auth-token": "^5.0.0", + "semver": "^7.1.2", + "tempy": "^3.0.0" + }, + "engines": { + "node": "^22.14.0 || >= 24.10.0" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" + } }, - "node_modules/@types/parse-json": { + "node_modules/@semantic-release/npm/node_modules/@semantic-release/error": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, - "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "node_modules/@semantic-release/npm/node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "node_modules/@semantic-release/npm/node_modules/aggregate-error": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", + "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "clean-stack": "^5.2.0", + "indent-string": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/clean-stack": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.3.0.tgz", + "integrity": "sha512-9ngPTOhYGQqNVSfeJkYXHmF7AGWp4/nN5D/QqNQs3Dvxd1Kk/WpjHfNujKHYUQ/5CoGyOyFNoWSPk5afzP0QVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "5.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@semantic-release/npm/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/hosted-git-info": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", + "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/lru-cache": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@semantic-release/npm/node_modules/normalize-package-data": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-8.0.0.tgz", + "integrity": "sha512-RWk+PI433eESQ7ounYxIp67CYuVsS1uYSonX3kA6ps/3LWfjVQa/ptEg6Y3T6uAMq1mWpX9PQ+qx+QaHpsc7gQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^9.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/parse-json/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/read-pkg": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-10.1.0.tgz", + "integrity": "sha512-I8g2lArQiP78ll51UeMZojewtYgIRCKCWqZEgOO8c/uefTI+XDXvCSXu3+YNUaTNvZzobrL5+SqHjBrByRRTdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.4", + "normalize-package-data": "^8.0.0", + "parse-json": "^8.3.0", + "type-fest": "^5.4.4", + "unicorn-magic": "^0.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/read-pkg/node_modules/unicorn-magic": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz", + "integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@semantic-release/npm/node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/type-fest": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.6.0.tgz", + "integrity": "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/release-notes-generator": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-14.1.0.tgz", + "integrity": "sha512-CcyDRk7xq+ON/20YNR+1I/jP7BYKICr1uKd1HHpROSnnTdGqOTburi4jcRiTYz0cpfhxSloQO3cGhnoot7IEkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "conventional-changelog-angular": "^8.0.0", + "conventional-changelog-writer": "^8.0.0", + "conventional-commits-filter": "^5.0.0", + "conventional-commits-parser": "^6.0.0", + "debug": "^4.0.0", + "get-stream": "^7.0.0", + "import-from-esm": "^2.0.0", + "into-stream": "^7.0.0", + "lodash-es": "^4.17.21", + "read-package-up": "^11.0.0" + }, + "engines": { + "node": ">=20.8.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" + } + }, + "node_modules/@semantic-release/release-notes-generator/node_modules/get-stream": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-7.0.1.tgz", + "integrity": "sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/release-notes-generator/node_modules/read-package-up": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz", + "integrity": "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up-simple": "^1.0.0", + "read-pkg": "^9.0.0", + "type-fest": "^4.6.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/release-notes-generator/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@simple-libs/stream-utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@simple-libs/stream-utils/-/stream-utils-1.2.0.tgz", + "integrity": "sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://ko-fi.com/dangreen" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.34.40", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.40.tgz", + "integrity": "sha512-gwBNIP8ZAYev/ORDWW0QvxdwPXwxBtLsdsJgSc7eDIRt8ubP+rxUBzPsrwnu16fgEF8Bx4lh/+mvQvJzcTM6Kw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "15.3.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.3.2.tgz", + "integrity": "sha512-mrn35Jl2pCpns+mE3HaZa1yPN5EYCRgiMI+135COjr2hr8Cls9DXqIZ57vZe2cz7y2XVSq92tcs6kGQcT1J8Rw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@textlint/ast-node-types": { + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-15.5.4.tgz", + "integrity": "sha512-bVtB6VEy9U9DpW8cTt25k5T+lz86zV5w6ImePZqY1AXzSuPhqQNT77lkMPxonXzUducEIlSvUu3o7sKw3y9+Sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/linter-formatter": { + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@textlint/linter-formatter/-/linter-formatter-15.5.4.tgz", + "integrity": "sha512-D9qJedKBLmAo+kiudop4UKgSxXMi4O8U86KrCidVXZ9RsK0NSVIw6+r2rlMUOExq79iEY81FRENyzmNVRxDBsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azu/format-text": "^1.0.2", + "@azu/style-format": "^1.0.1", + "@textlint/module-interop": "15.5.4", + "@textlint/resolver": "15.5.4", + "@textlint/types": "15.5.4", + "chalk": "^4.1.2", + "debug": "^4.4.3", + "js-yaml": "^4.1.1", + "lodash": "^4.18.1", + "pluralize": "^2.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "table": "^6.9.0", + "text-table": "^0.2.0" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/ansi-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/@textlint/linter-formatter/node_modules/pluralize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-2.0.0.tgz", + "integrity": "sha512-TqNZzQCD4S42De9IfnnBvILN7HAW7riLqsCyp8lgjXeysyPlX5HhqKAcJHHHb9XskE4/a+7VGC9zzx8Ls0jOAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/linter-formatter/node_modules/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/@textlint/module-interop": { + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@textlint/module-interop/-/module-interop-15.5.4.tgz", + "integrity": "sha512-JyAUd26ll3IFF87LP0uGoa8Tzw5ZKiYvGs6v8jLlzyND1lUYCI4+2oIAslrODLkf0qwoCaJrBQWM3wsw+asVGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/resolver": { + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@textlint/resolver/-/resolver-15.5.4.tgz", + "integrity": "sha512-5GUagtpQuYcmhlOzBGdmVBvDu5lKgVTjwbxtdfoidN4OIqblIxThJHHjazU+ic+/bCIIzI2JcOjHGSaRmE8Gcg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/types": { + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@textlint/types/-/types-15.5.4.tgz", + "integrity": "sha512-mY28j2U7nrWmZbxyKnRvB8vJxJab4AxqOobLfb6iozrLelJbqxcOTvBQednadWPfAk9XWaZVMqUr9Nird3mutg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@textlint/ast-node-types": "15.5.4" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "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/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^30.0.0", + "pretty-format": "^30.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.12.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", + "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/sarif": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@types/sarif/-/sarif-2.1.7.tgz", + "integrity": "sha512-kRz0VEkJqWLf1LLVN4pT1cg1Z9wAuvI6L97V3m2f5B76Tg8d413ddvLBPTEHAZJlnn4XSvu0FkZtViCQGVyrXQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" }, "node_modules/@types/vscode": { - "version": "1.74.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.74.0.tgz", - "integrity": "sha512-LyeCIU3jb9d38w0MXFwta9r0Jx23ugujkAxdwLTNCyspdZTKUc43t7ppPbCiPoQ/Ivd/pnDFZrb4hWd45wrsgA==", + "version": "1.116.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.116.0.tgz", + "integrity": "sha512-sYHp4MO6BqJ2PD7Hjt0hlIS3tMaYsVPJrd0RUjDJ8HtOYnyJIEej0bLSccM8rE77WrC+Xox/kdBwEFDO8MsxNA==", "dev": true, "license": "MIT" }, @@ -2015,6 +3046,45 @@ "dev": true, "license": "MIT" }, + "node_modules/@typespec/ts-http-runtime": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.5.tgz", + "integrity": "sha512-yURCknZhvywvQItHMMmFSo+fq5arCUIyz/CVk7jD89MSai7dkaX8ufjCWp3NttLojoTVbcE72ri+be/TnEbMHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@typespec/ts-http-runtime/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/@typespec/ts-http-runtime/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/@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", @@ -2142,223 +3212,506 @@ "arm64" ], "dev": true, - "license": "MIT", + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vscode/vsce": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.9.0.tgz", + "integrity": "sha512-Dfql2kgPHpTKS+G4wTqYBKzK1KqX2gMxTC9dpnYge+aIZL+thh1Z6GXs4zOtNwo7DCPmS7BCfaHUAmNLxKiXkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/identity": "^4.1.0", + "@secretlint/node": "^10.1.2", + "@secretlint/secretlint-formatter-sarif": "^10.1.2", + "@secretlint/secretlint-rule-no-dotenv": "^10.1.2", + "@secretlint/secretlint-rule-preset-recommend": "^10.1.2", + "@vscode/vsce-sign": "^2.0.0", + "azure-devops-node-api": "^12.5.0", + "chalk": "^4.1.2", + "cheerio": "^1.0.0-rc.9", + "cockatiel": "^3.1.2", + "commander": "^12.1.0", + "form-data": "^4.0.0", + "glob": "^11.0.0", + "hosted-git-info": "^4.0.2", + "jsonc-parser": "^3.2.0", + "leven": "^3.1.0", + "markdown-it": "^14.1.0", + "mime": "^1.3.4", + "minimatch": "^3.0.3", + "parse-semver": "^1.1.1", + "read": "^1.0.7", + "secretlint": "^10.1.2", + "semver": "^7.5.2", + "tmp": "^0.2.3", + "typed-rest-client": "^1.8.4", + "url-join": "^4.0.1", + "xml2js": "^0.5.0", + "yauzl": "^3.2.1", + "yazl": "^2.2.2" + }, + "bin": { + "vsce": "vsce" + }, + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "keytar": "^7.7.0" + } + }, + "node_modules/@vscode/vsce-sign": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign/-/vsce-sign-2.0.9.tgz", + "integrity": "sha512-8IvaRvtFyzUnGGl3f5+1Cnor3LqaUWvhaUjAYO8Y39OUYlOf3cRd+dowuQYLpZcP3uwSG+mURwjEBOSq4SOJ0g==", + "dev": true, + "hasInstallScript": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optionalDependencies": { + "@vscode/vsce-sign-alpine-arm64": "2.0.6", + "@vscode/vsce-sign-alpine-x64": "2.0.6", + "@vscode/vsce-sign-darwin-arm64": "2.0.6", + "@vscode/vsce-sign-darwin-x64": "2.0.6", + "@vscode/vsce-sign-linux-arm": "2.0.6", + "@vscode/vsce-sign-linux-arm64": "2.0.6", + "@vscode/vsce-sign-linux-x64": "2.0.6", + "@vscode/vsce-sign-win32-arm64": "2.0.6", + "@vscode/vsce-sign-win32-x64": "2.0.6" + } + }, + "node_modules/@vscode/vsce-sign-alpine-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-arm64/-/vsce-sign-alpine-arm64-2.0.6.tgz", + "integrity": "sha512-wKkJBsvKF+f0GfsUuGT0tSW0kZL87QggEiqNqK6/8hvqsXvpx8OsTEc3mnE1kejkh5r+qUyQ7PtF8jZYN0mo8Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "alpine" + ] + }, + "node_modules/@vscode/vsce-sign-alpine-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-x64/-/vsce-sign-alpine-x64-2.0.6.tgz", + "integrity": "sha512-YoAGlmdK39vKi9jA18i4ufBbd95OqGJxRvF3n6ZbCyziwy3O+JgOpIUPxv5tjeO6gQfx29qBivQ8ZZTUF2Ba0w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "alpine" + ] + }, + "node_modules/@vscode/vsce-sign-darwin-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-arm64/-/vsce-sign-darwin-arm64-2.0.6.tgz", + "integrity": "sha512-5HMHaJRIQuozm/XQIiJiA0W9uhdblwwl2ZNDSSAeXGO9YhB9MH5C4KIHOmvyjUnKy4UCuiP43VKpIxW1VWP4tQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", "optional": true, "os": [ - "linux" + "darwin" ] }, - "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", - "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "node_modules/@vscode/vsce-sign-darwin-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-x64/-/vsce-sign-darwin-x64-2.0.6.tgz", + "integrity": "sha512-25GsUbTAiNfHSuRItoQafXOIpxlYj+IXb4/qarrXu7kmbH94jlm5sdWSCKrrREs8+GsXF1b+l3OB7VJy5jsykw==", "cpu": [ - "ppc64" + "x64" ], "dev": true, - "license": "MIT", + "license": "SEE LICENSE IN LICENSE.txt", "optional": true, "os": [ - "linux" + "darwin" ] }, - "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", - "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "node_modules/@vscode/vsce-sign-linux-arm": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm/-/vsce-sign-linux-arm-2.0.6.tgz", + "integrity": "sha512-UndEc2Xlq4HsuMPnwu7420uqceXjs4yb5W8E2/UkaHBB9OWCwMd3/bRe/1eLe3D8kPpxzcaeTyXiK3RdzS/1CA==", "cpu": [ - "riscv64" + "arm" ], "dev": true, - "license": "MIT", + "license": "SEE LICENSE IN LICENSE.txt", "optional": true, "os": [ "linux" ] }, - "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", - "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "node_modules/@vscode/vsce-sign-linux-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm64/-/vsce-sign-linux-arm64-2.0.6.tgz", + "integrity": "sha512-cfb1qK7lygtMa4NUl2582nP7aliLYuDEVpAbXJMkDq1qE+olIw/es+C8j1LJwvcRq1I2yWGtSn3EkDp9Dq5FdA==", "cpu": [ - "riscv64" + "arm64" ], "dev": true, - "license": "MIT", + "license": "SEE LICENSE IN LICENSE.txt", "optional": true, "os": [ "linux" ] }, - "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", - "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "node_modules/@vscode/vsce-sign-linux-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-x64/-/vsce-sign-linux-x64-2.0.6.tgz", + "integrity": "sha512-/olerl1A4sOqdP+hjvJ1sbQjKN07Y3DVnxO4gnbn/ahtQvFrdhUi0G1VsZXDNjfqmXw57DmPi5ASnj/8PGZhAA==", "cpu": [ - "s390x" + "x64" ], "dev": true, - "license": "MIT", + "license": "SEE LICENSE IN LICENSE.txt", "optional": true, "os": [ "linux" ] }, - "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", - "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "node_modules/@vscode/vsce-sign-win32-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-arm64/-/vsce-sign-win32-arm64-2.0.6.tgz", + "integrity": "sha512-ivM/MiGIY0PJNZBoGtlRBM/xDpwbdlCWomUWuLmIxbi1Cxe/1nooYrEQoaHD8ojVRgzdQEUzMsRbyF5cJJgYOg==", "cpu": [ - "x64" + "arm64" ], "dev": true, - "license": "MIT", + "license": "SEE LICENSE IN LICENSE.txt", "optional": true, "os": [ - "linux" + "win32" ] }, - "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", - "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "node_modules/@vscode/vsce-sign-win32-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-x64/-/vsce-sign-win32-x64-2.0.6.tgz", + "integrity": "sha512-mgth9Kvze+u8CruYMmhHw6Zgy3GRX2S+Ed5oSokDEK5vPEwGGKnmuXua9tmFhomeAnhgJnL4DCna3TiNuGrBTQ==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", + "license": "SEE LICENSE IN LICENSE.txt", "optional": true, "os": [ - "linux" + "win32" ] }, - "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", - "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", - "cpu": [ - "wasm32" - ], + "node_modules/@vscode/vsce/node_modules/azure-devops-node-api": { + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-12.5.0.tgz", + "integrity": "sha512-R5eFskGvOm3U/GzeAuxRkUsAl0hrAwGgWn6zAd2KrZmrEhWZVqLew4OOupbQlXUuojUzpGtq62SmdhJ06N88og==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.11" - }, + "tunnel": "0.0.6", + "typed-rest-client": "^1.8.4" + } + }, + "node_modules/@vscode/vsce/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, - "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", - "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", - "cpu": [ - "arm64" - ], + "node_modules/@vscode/vsce/node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "uc.micro": "^2.0.0" + } }, - "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", - "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", - "cpu": [ - "ia32" - ], + "node_modules/@vscode/vsce/node_modules/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } }, - "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", - "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", - "cpu": [ - "x64" - ], + "node_modules/@vscode/vsce/node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vscode/vsce/node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vscode/vsce/node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/@vscode/vsce/node_modules/yauzl": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.3.0.tgz", + "integrity": "sha512-PtGEvEP30p7sbIBJKUBjUnqgTVOyMURc4dLo9iNyAJnNIEz9pm88cCXF21w94Kg3k6RXkeZh5DHOGS0qEONvNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "pend": "~1.2.0" + }, + "engines": { + "node": ">=12" + } }, "node_modules/@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "dev": true, "license": "MIT", "dependencies": { @@ -2366,9 +3719,9 @@ } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2376,129 +3729,82 @@ } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, - "node_modules/@webpack-cli/configtest": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.0.1.tgz", - "integrity": "sha512-njsdJXJSiS2iNbQVS0eT8A/KPnmyH4pv1APj2K0d1wrZcBLw+yppxOy4CGqa0OxDJkzfL/XELDhD8rocnIwB5A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - } - }, - "node_modules/@webpack-cli/info": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.1.tgz", - "integrity": "sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - } - }, - "node_modules/@webpack-cli/serve": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.1.tgz", - "integrity": "sha512-0G7tNyS+yW8TdgHwZKlDWYXFA6OJQnoLCQvYKkQP0Q2X205PSQ6RNUj0M+1OB/9gRQaUZ/ccYfaxd0nhaWKfjw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - }, - "peerDependenciesMeta": { - "webpack-dev-server": { - "optional": true - } - } - }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -2514,9 +3820,9 @@ "license": "Apache-2.0" }, "node_modules/acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "peer": true, @@ -2527,28 +3833,17 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "deprecated": "package has been renamed to acorn-import-attributes", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^8" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "dev": true, "license": "MIT", - "dependencies": { - "debug": "4" - }, "engines": { - "node": ">= 6.0.0" + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" } }, "node_modules/aggregate-error": { @@ -2566,31 +3861,52 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", "peer": true, "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" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, "funding": { "type": "github", "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, "peerDependencies": { - "ajv": "^6.9.1" + "ajv": "^8.8.2" } }, "node_modules/ansi-escapes": { @@ -2609,27 +3925,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "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==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ansi-styles": { @@ -2648,10 +3954,10 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/ansicolors": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", - "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==", + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", "dev": true, "license": "MIT" }, @@ -2690,48 +3996,34 @@ "dev": true, "license": "MIT" }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/azure-devops-node-api": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-11.2.0.tgz", - "integrity": "sha512-XdiGPhrpaT5J8wdERRKs5g8E0Zy1pvOYTli7z9E8nmOn3YGp4FhtjhrOyFmX/8veWCwdI69mCHKJw6l+4J/bHA==", + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true, - "license": "MIT", - "dependencies": { - "tunnel": "0.0.6", - "typed-rest-client": "^1.8.4" - } + "license": "MIT" }, "node_modules/babel-jest": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.1.1.tgz", - "integrity": "sha512-1bZfC/V03qBCzASvZpNFhx3Ouj6LgOd4KFJm4br/fYOS+tSSvVCE61QmcAVbMTwq/GoB7KN4pzGMoyr9cMxSvQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.3.0.tgz", + "integrity": "sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/transform": "30.1.1", + "@jest/transform": "30.3.0", "@types/babel__core": "^7.20.5", - "babel-plugin-istanbul": "^7.0.0", - "babel-preset-jest": "30.0.1", + "babel-plugin-istanbul": "^7.0.1", + "babel-preset-jest": "30.3.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "slash": "^3.0.0" @@ -2740,15 +4032,18 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.11.0" + "@babel/core": "^7.11.0 || ^8.0.0-0" } }, "node_modules/babel-plugin-istanbul": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.0.tgz", - "integrity": "sha512-C5OzENSx/A+gt7t4VH1I2XsflxyPUmXRFPKBxt33xncdOmq7oROVM3bZv9Ysjjkv8OJYDMa+tKuKMvqU/H3xdw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", "dev": true, "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", @@ -2761,14 +4056,12 @@ } }, "node_modules/babel-plugin-jest-hoist": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.0.1.tgz", - "integrity": "sha512-zTPME3pI50NsFW8ZBaVIOeAxzEY7XHlmWeXXu9srI+9kNfzCUTy8MFan46xOGZY8NZThMqq+e3qZUKsvXbasnQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.3.0.tgz", + "integrity": "sha512-+TRkByhsws6sfPjVaitzadk1I0F5sPvOVUH5tyTSzhePpsGIVrdeunHSw/C36QeocS95OOk8lunc4rlu5Anwsg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", "@types/babel__core": "^7.20.5" }, "engines": { @@ -2803,20 +4096,20 @@ } }, "node_modules/babel-preset-jest": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.0.1.tgz", - "integrity": "sha512-+YHejD5iTWI46cZmcc/YtX4gaKBtdqCHCVfuVinizVpbmyjO3zYmeuyFdfA8duRqQZfgCAMlsfmkVbJ+e2MAJw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.3.0.tgz", + "integrity": "sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ==", "dev": true, "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "30.0.1", - "babel-preset-current-node-syntax": "^1.1.0" + "babel-plugin-jest-hoist": "30.3.0", + "babel-preset-current-node-syntax": "^1.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.11.0" + "@babel/core": "^7.11.0 || ^8.0.0-beta.1" } }, "node_modules/balanced-match": { @@ -2845,21 +4138,52 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "optional": true + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.19.tgz", + "integrity": "sha512-qCkNLi2sfBOn8XhZQ0FXsT1Ki/Yo5P90hrkRamVFRS7/KV9hpfA4HkoWNU152+8w0zPjnxo5psx5NL3PSGgv5g==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } }, "node_modules/before-after-hook": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", - "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", + "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", "dev": true, "license": "Apache-2.0" }, + "node_modules/binaryextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-6.11.0.tgz", + "integrity": "sha512-sXnYK/Ij80TO3lcqZVV2YgfKN5QjUWIRk/XSm2J/4bd/lPko3lvk0O4ZppH6m+6hB2/GTu+ptNwVFe1xh+QLQw==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -2872,6 +4196,7 @@ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -2895,10 +4220,17 @@ "dev": true, "license": "MIT" }, + "node_modules/boundary": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/boundary/-/boundary-2.0.0.tgz", + "integrity": "sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -2919,17 +4251,10 @@ "node": ">=8" } }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true, - "license": "ISC" - }, "node_modules/browserslist": { - "version": "4.25.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.4.tgz", - "integrity": "sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==", + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", "dev": true, "funding": [ { @@ -2948,10 +4273,11 @@ "license": "MIT", "peer": true, "dependencies": { - "caniuse-lite": "^1.0.30001737", - "electron-to-chromium": "^1.5.211", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -3003,6 +4329,7 @@ } ], "license": "MIT", + "optional": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -3018,6 +4345,13 @@ "node": "*" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -3025,15 +4359,48 @@ "dev": true, "license": "MIT" }, - "node_modules/call-bind": { + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/call-bind-apply-helpers": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "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": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3059,28 +4426,10 @@ "node": ">=6" } }, - "node_modules/camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/caniuse-lite": { - "version": "1.0.30001737", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001737.tgz", - "integrity": "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw==", + "version": "1.0.30001788", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001788.tgz", + "integrity": "sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==", "dev": true, "funding": [ { @@ -3098,20 +4447,6 @@ ], "license": "CC-BY-4.0" }, - "node_modules/cardinal": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", - "integrity": "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansicolors": "~0.3.2", - "redeyed": "~2.1.0" - }, - "bin": { - "cdl": "bin/cdl.js" - } - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -3184,7 +4519,8 @@ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "dev": true, - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/chrome-trace-event": { "version": "1.0.3", @@ -3213,9 +4549,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.1.0.tgz", - "integrity": "sha512-UX0OwmYRYQQetfrLEZeewIFFI+wSTofC+pMBLNuH3RUuu/xzG1oz84UCEDOSoQlN3fZ4+AzmV50ZYvGqkMh9yA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", "dev": true, "license": "MIT" }, @@ -3229,44 +4565,132 @@ "node": ">=6" } }, - "node_modules/cli-table3": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", - "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "string-width": "^4.2.0" + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" }, - "engines": { - "node": "10.* || >= 12.*" + "bin": { + "highlight": "bin/highlight" }, - "optionalDependencies": { - "@colors/colors": "1.5.0" + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" } }, - "node_modules/cli-table3/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "node_modules/cli-highlight/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/cli-highlight/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cli-highlight/node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", "dev": true, "license": "MIT" }, - "node_modules/cli-table3/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/cli-highlight/node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "parse5": "^6.0.1" + } + }, + "node_modules/cli-highlight/node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-highlight/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/cli-highlight/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-highlight/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -3282,44 +4706,27 @@ "node": ">=12" } }, - "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==", + "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", - "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/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/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-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">=8" } }, "node_modules/clone-deep": { @@ -3337,19 +4744,6 @@ "node": ">=6" } }, - "node_modules/clone-deep/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -3361,10 +4755,20 @@ "node": ">= 0.12.0" } }, + "node_modules/cockatiel": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.2.1.tgz", + "integrity": "sha512-gfrHV6ZPkquExvMh9IOkKsBzNDk6sDuZ6DdBGUBkvFnTCqCxzpuq48RySgP0AnaqQkw2zynOFj9yly6T1Q2G5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", "dev": true, "license": "MIT" }, @@ -3388,21 +4792,47 @@ "dev": true, "license": "MIT" }, - "node_modules/colorette": { - "version": "2.0.16", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", - "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==", + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commit-msg-linter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/commit-msg-linter/-/commit-msg-linter-1.1.0.tgz", + "integrity": "sha512-fLdMKN12G/f+ZZ+TCRBiK/yon/SmxMIDizJI3PbYSETxq6DBF/zqok3cHPoBCiDysUVyJ8WpMokrP0t34qJm8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "did-you-mean": "^0.0.1", + "supports-color": "^8.1.1" + }, + "engines": { + "node": ">= 14.0.0" + } }, - "node_modules/commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "node_modules/commit-msg-linter/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": ">= 6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/compare-func": { @@ -3423,87 +4853,88 @@ "dev": true, "license": "MIT" }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, "node_modules/conventional-changelog-angular": { - "version": "5.0.13", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz", - "integrity": "sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==", + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.3.1.tgz", + "integrity": "sha512-6gfI3otXK5Ph5DfCOI1dblr+kN3FAm5a97hYoQkqNZxOaYa5WKfXH+AnpsmS+iUH2mgVC2Cg2Qw9m5OKcmNrIg==", "dev": true, "license": "ISC", "dependencies": { - "compare-func": "^2.0.0", - "q": "^1.5.1" + "compare-func": "^2.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/conventional-changelog-writer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-5.0.1.tgz", - "integrity": "sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.4.0.tgz", + "integrity": "sha512-HHBFkk1EECxxmCi4CTu091iuDpQv5/OavuCUAuZmrkWpmYfyD816nom1CvtfXJ/uYfAAjavgHvXHX291tSLK8g==", "dev": true, "license": "MIT", "dependencies": { - "conventional-commits-filter": "^2.0.7", - "dateformat": "^3.0.0", + "@simple-libs/stream-utils": "^1.2.0", + "conventional-commits-filter": "^5.0.0", "handlebars": "^4.7.7", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "semver": "^6.0.0", - "split": "^1.0.0", - "through2": "^4.0.0" + "meow": "^13.0.0", + "semver": "^7.5.2" }, "bin": { - "conventional-changelog-writer": "cli.js" + "conventional-changelog-writer": "dist/cli/index.js" }, "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-writer/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node": ">=18" } }, "node_modules/conventional-commits-filter": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz", - "integrity": "sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz", + "integrity": "sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==", "dev": true, "license": "MIT", - "dependencies": { - "lodash.ismatch": "^4.4.0", - "modify-values": "^1.0.0" - }, "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/conventional-commits-parser": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.2.4.tgz", - "integrity": "sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.4.0.tgz", + "integrity": "sha512-tvRg7FIBNlyPzjdG8wWRlPHQJJHI7DylhtRGeU9Lq+JuoPh5BKpPRX83ZdLrvXuOSu5Eo/e7SzOQhU4Hd2Miuw==", "dev": true, "license": "MIT", "dependencies": { - "is-text-path": "^1.0.1", - "JSONStream": "^1.0.4", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "split2": "^3.0.0", - "through2": "^4.0.0" + "@simple-libs/stream-utils": "^1.2.0", + "meow": "^13.0.0" }, "bin": { - "conventional-commits-parser": "cli.js" + "conventional-commits-parser": "dist/cli/index.js" }, "engines": { - "node": ">=10" + "node": ">=18" + } + }, + "node_modules/convert-hrtime": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", + "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/convert-source-map": { @@ -3521,20 +4952,30 @@ "license": "MIT" }, "node_modules/cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", + "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" }, "engines": { - "node": ">=10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/cross-spawn": { @@ -3553,13 +4994,32 @@ } }, "node_modules/crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", "dev": true, "license": "MIT", + "dependencies": { + "type-fest": "^1.0.1" + }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/crypto-random-string/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/css-select": { @@ -3592,24 +5052,14 @@ "url": "https://github.com/sponsors/fb55" } }, - "node_modules/dateformat": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", - "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -3620,49 +5070,13 @@ } } }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decamelize-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", - "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", - "dev": true, - "license": "MIT", - "dependencies": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decamelize-keys/node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { "mimic-response": "^3.1.0" }, @@ -3674,9 +5088,9 @@ } }, "node_modules/dedent": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", - "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", "dev": true, "license": "MIT", "peerDependencies": { @@ -3708,51 +5122,58 @@ "node": ">=0.10.0" } }, - "node_modules/del": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", - "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==", + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", "dev": true, "license": "MIT", "dependencies": { - "globby": "^11.0.1", - "graceful-fs": "^4.2.4", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.2", - "p-map": "^4.0.0", - "rimraf": "^3.0.2", - "slash": "^3.0.0" + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/del/node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", "dev": true, "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" + "engines": { + "node": ">=18" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/deprecation": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", - "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, - "license": "ISC" + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } }, "node_modules/detect-libc": { "version": "2.0.1", @@ -3760,6 +5181,7 @@ "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", "dev": true, "license": "Apache-2.0", + "optional": true, "engines": { "node": ">=8" } @@ -3777,7 +5199,7 @@ "node_modules/did-you-mean": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/did-you-mean/-/did-you-mean-0.0.1.tgz", - "integrity": "sha1-iFHOgkB5A8tiwSy2rU9naSHM3sM= sha512-rKxPpbrHr4/u8VMPde6Z3DhbGoXw7kepHZIU2FGs+IU+LKnhEIlw1cx80FZ7fLruJXDyu0slKMai2FhPRZjwxQ==", + "integrity": "sha512-rKxPpbrHr4/u8VMPde6Z3DhbGoXw7kepHZIU2FGs+IU+LKnhEIlw1cx80FZ7fLruJXDyu0slKMai2FhPRZjwxQ==", "dev": true, "license": "ISC", "dependencies": { @@ -3785,16 +5207,6 @@ "underscore": "*" } }, - "node_modules/diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -3880,6 +5292,21 @@ "node": ">=8" } }, + "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/duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", @@ -3897,89 +5324,277 @@ "dev": true, "license": "MIT" }, - "node_modules/electron-to-chromium": { - "version": "1.5.211", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.211.tgz", - "integrity": "sha512-IGBvimJkotaLzFnwIVgW9/UD/AOJ2tByUmeOrtqBfACSbAw5b1G0XpvdaieKyc7ULmbwXVx+4e4Be8pOPBrYkw==", + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/editions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/editions/-/editions-6.22.0.tgz", + "integrity": "sha512-UgGlf8IW75je7HZjNDpJdCv4cGJWIi6yumFdZ0R7A8/CIhQiWUjyGLCxdHpd8bmyD1gnkfUNK0oeOXqUS2cpfQ==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "version-range": "^4.15.0" + }, + "engines": { + "ecmascript": ">= es5", + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.340", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.340.tgz", + "integrity": "sha512-908qahOGocRMinT2nM3ajCEM99H4iPdv84eagPP3FfZy/1ZGeOy2CZYzjhms81ckOPCXPlW7LkY4XpxD8r1DrA==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "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/emojilib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", + "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", + "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-ci": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-11.2.0.tgz", + "integrity": "sha512-D5kWfzkmaOQDioPmiviWAVtKmpPT4/iJmMVQxWxMPJTFyTkdc5JQUfc5iXEeWxcOdsYTKSAiA/Age4NUOqKsRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^8.0.0", + "java-properties": "^1.0.2" + }, + "engines": { + "node": "^18.17 || >=20.6.1" + } + }, + "node_modules/env-ci/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/env-ci/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/env-ci/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", "dev": true, - "license": "ISC" + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "node_modules/env-ci/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "node_modules/env-ci/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "node_modules/env-ci/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", "dev": true, "license": "MIT", "dependencies": { - "once": "^1.4.0" + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/enhanced-resolve": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", - "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", + "node_modules/env-ci/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", "dev": true, "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "mimic-fn": "^4.0.0" }, "engines": { - "node": ">=10.13.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/entities": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", - "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "node_modules/env-ci/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "engines": { - "node": ">=0.12" + "node": ">=12" }, "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/env-ci": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-5.5.0.tgz", - "integrity": "sha512-o0JdWIbOLP+WJKIUt36hz1ImQQFuN92nhsfTkHHap+J8CiI8WgGpH/a9jEGHh4/TU5BUUGjlnKXNoDb57+ne+A==", + "node_modules/env-ci/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/env-ci/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", "dev": true, "license": "MIT", - "dependencies": { - "execa": "^5.0.0", - "fromentries": "^1.3.2", - "java-properties": "^1.0.0" + "engines": { + "node": ">=12" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=10.17" + "node": ">=6" } }, "node_modules/envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", + "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", "dev": true, "license": "MIT", "bin": { @@ -3989,6 +5604,19 @@ "node": ">=4" } }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -3999,28 +5627,60 @@ "is-arrayish": "^0.2.1" } }, + "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-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "dev": true, "license": "MIT" }, - "node_modules/es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "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" + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } }, - "node_modules/es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", + "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": { - "es6-promise": "^4.0.3" + "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/escalade": { @@ -4033,6 +5693,19 @@ "node": ">=6" } }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -4144,28 +5817,46 @@ "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", "dev": true, "license": "(MIT OR WTFPL)", + "optional": true, "engines": { "node": ">=6" } }, "node_modules/expect": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.1.1.tgz", - "integrity": "sha512-OKe7cdic4qbfWd/CcgwJvvCrNX2KWfuMZee9AfJHL1gTYmvqjBjZG1a2NwfhspBzxzlXwsN75WWpKTYfsJpBxg==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==", "dev": true, "license": "MIT", "dependencies": { - "@jest/expect-utils": "30.1.1", + "@jest/expect-utils": "30.3.0", "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.1.1", - "jest-message-util": "30.1.0", - "jest-mock": "30.0.5", - "jest-util": "30.0.5" + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/fast-content-type-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", + "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4174,9 +5865,9 @@ "license": "MIT" }, "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "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": { @@ -4184,25 +5875,12 @@ "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "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", @@ -4210,6 +5888,23 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fastest-levenshtein": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", @@ -4218,9 +5913,9 @@ "license": "MIT" }, "node_modules/fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "dev": true, "license": "ISC", "dependencies": { @@ -4237,42 +5932,22 @@ "bser": "2.1.1" } }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } - }, "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", "dev": true, "license": "MIT", "dependencies": { - "escape-string-regexp": "^1.0.5" + "is-unicode-supported": "^2.0.0" }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/figures/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -4286,22 +5961,60 @@ "node": ">=8" } }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up-simple": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", + "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/find-versions": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-4.0.0.tgz", - "integrity": "sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-6.0.0.tgz", + "integrity": "sha512-2kCCtc+JvcZ86IGAz3Z2Y0A1baIz9fL31pH/0S1IqZr9Iwnjq8izfPtrCyQKO6TLMPELLsQMre7VDqeIKCsHkA==", "dev": true, "license": "MIT", "dependencies": { - "semver-regex": "^3.1.2" + "semver-regex": "^4.0.5", + "super-regex": "^1.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "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/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -4332,55 +6045,46 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "dev": true, "license": "MIT", "dependencies": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" } }, - "node_modules/fromentries": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", - "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", "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/fs": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.2.tgz", - "integrity": "sha512-YAiVokMCrSIFZiroB1oz51hPiPRVcUtSa4x2U5RYXyhS9VAPdiFigKbPTnOSq7XY8wd3FIVPYmXpo5lMzFmxgg==", - "license": "MIT" + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/fs-extra": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz", - "integrity": "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==", + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", "dev": true, "license": "MIT", "dependencies": { @@ -4415,11 +6119,27 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "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" + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function-timeout": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-1.0.2.tgz", + "integrity": "sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/gensync": { "version": "1.0.0-beta.2", @@ -4441,16 +6161,39 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "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": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" + "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" @@ -4466,6 +6209,20 @@ "node": ">=8.0.0" } }, + "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-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -4480,19 +6237,21 @@ } }, "node_modules/git-commit-msg-linter": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/git-commit-msg-linter/-/git-commit-msg-linter-4.5.0.tgz", - "integrity": "sha512-jEVRNeiuCLjgsd0srXlfN2syNaDbCEoqvbs3YaVcAwb6dstpMoUjguv2MN5anec8DG9SgWZe5jmpT4H4iv5IcA==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/git-commit-msg-linter/-/git-commit-msg-linter-5.0.8.tgz", + "integrity": "sha512-buif1Hfo3barMYIOz9kKbyD6upC4/bQSDVPuGMIXho3bAgaTCG0fPhOVq9JAFf9XAfcpRWqgIK1Q+BB7exNdFA==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { "chalk": "^2.4.2", - "did-you-mean": "^0.0.1", - "supports-color": "^8.1.1" + "commit-msg-linter": "^1.0.0" + }, + "bin": { + "commit-msg-linter": "cli/validate.js" }, "engines": { - "node": ">= 8.0.0" + "node": ">= 14.0.0" } }, "node_modules/git-commit-msg-linter/node_modules/ansi-styles": { @@ -4573,22 +6332,6 @@ "node": ">=0.8.0" } }, - "node_modules/git-commit-msg-linter/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/git-log-parser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.0.tgz", @@ -4630,1874 +6373,1776 @@ "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "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==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.x" - } - }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0= sha512-z/GDPjlRMNOa2XJiB4em8wJpuuBfrFOlYKTZxtpkdr1uPdibHI8rYA3MY0KDObpVyaes0e/aunid/t88ZI2EKA==", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, - "node_modules/hook-std": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-2.0.0.tgz", - "integrity": "sha512-zZ6T5WcuBMIUVh49iPQS9t977t7C0l7OtHrpeMb5uk48JdflRX0NSFvCekfYNmGQETnLq9W/isMyHl69kxGi8g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "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": { - "yallist": "^4.0.0" + "is-glob": "^4.0.1" }, "engines": { - "node": ">=10" + "node": ">= 6" } }, - "node_modules/hosted-git-info/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/htmlparser2": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz", - "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==", + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "entities": "^4.3.0" + "engines": { + "node": "18 || 20 || >=22" } }, - "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "node_modules/glob/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" + "balanced-match": "^4.0.2" }, "engines": { - "node": ">= 6" + "node": "18 || 20 || >=22" } }, - "node_modules/https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "node_modules/glob/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "dependencies": { - "agent-base": "6", - "debug": "4" + "brace-expansion": "^5.0.5" }, "engines": { - "node": ">= 6" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "node_modules/globby": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + }, "engines": { - "node": ">=10.17.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "node_modules/globby/node_modules/path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 4" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "node_modules/globby/node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", "dev": true, "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, "engines": { - "node": ">=6" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-from/-/import-from-4.0.0.tgz", - "integrity": "sha512-P9J71vT5nLlDeV8FHs5nNxaLbrpfAV5cF5srvbZfpwpcJoM/xZR3hiv+q+SAnuSmuGbXMWud063iIMx/V/EWZQ==", + "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": ">=12.2" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, + "license": "ISC" + }, + "node_modules/handlebars": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", "license": "MIT", "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" }, "bin": { - "import-local-fixture": "fixtures/cli.js" + "handlebars": "bin/handlebars" }, "engines": { - "node": ">=8" + "node": ">=0.4.7" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "uglify-js": "^3.1.4" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o= sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "license": "MIT", + "dependencies": { + "function-bind": "^1.1.1" + }, "engines": { - "node": ">=0.8.19" + "node": ">= 0.4.0" } }, - "node_modules/indent-string": { + "node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "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/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" - }, - "node_modules/interpret": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", - "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "node_modules/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": ">=10.13.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/into-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-6.0.0.tgz", - "integrity": "sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==", + "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": { - "from2": "^2.3.0", - "p-is-promise": "^3.0.0" + "has-symbols": "^1.0.3" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "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": { - "has": "^1.0.3" + "function-bind": "^1.1.2" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">= 0.4" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "engines": { - "node": ">=0.10.0" + "node": "*" } }, - "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/hook-std": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-4.0.0.tgz", + "integrity": "sha512-IHI4bEVOt3vRUDJ+bFA9VUJlo7SzvFARPNLw75pqSmAOP2HmTWfFJtPvLBrDrlgjEYXY9zs7SFdHPQaJShkSCQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", "dev": true, - "license": "MIT", + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, "engines": { - "node": ">=6" + "node": ">=10" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "is-extglob": "^2.1.1" + "yallist": "^4.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/hosted-git-info/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/htmlparser2": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz", + "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==", "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], "license": "MIT", - "engines": { - "node": ">=0.12.0" + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "entities": "^4.3.0" } }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "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": ">=8" + "node": ">= 14" } }, - "node_modules/is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "node_modules/http-proxy-agent/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": ">=6" + "node": ">= 14" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": ">=8" + "node": ">=10.17.0" } }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause", + "optional": true }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 4" } }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "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": ">=8" + "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-text-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", - "integrity": "sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==", + "node_modules/import-fresh/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", - "dependencies": { - "text-extensions": "^1.0.0" - }, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { + "node_modules/import-from-esm": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8= sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "resolved": "https://registry.npmjs.org/import-from-esm/-/import-from-esm-2.0.0.tgz", + "integrity": "sha512-YVt14UZCgsX1vZQ3gKjkWVdBdHQ6eu3MPU1TBgL1H5orXe2+jWD006WCPPtOuwlQm10NuzOW5WawiF1Q9veW8g==", "dev": true, "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "import-meta-resolve": "^4.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=18.20" } }, - "node_modules/issue-parser": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-6.0.0.tgz", - "integrity": "sha512-zKa/Dxq2lGsBIXQ7CUZWTHfvxPC2ej0KfO7fIPqLlHB9J2hJ7rGhZ5rilhuufylr4RXYPzJUeFjKxz305OsNlA==", + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", "dev": true, "license": "MIT", "dependencies": { - "lodash.capitalize": "^4.2.1", - "lodash.escaperegexp": "^4.1.2", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.uniqby": "^4.7.0" + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" }, - "engines": { - "node": ">=10.13" - } - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", "engines": { "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" }, - "engines": { - "node": ">=10" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o= sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - }, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=0.8.19" } }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "node_modules/index-to-position": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", + "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" + "license": "MIT", + "engines": { + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/java-properties": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz", - "integrity": "sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==", + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6.0" + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" } }, - "node_modules/jest": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.1.1.tgz", - "integrity": "sha512-yC3JvpP/ZcAZX5rYCtXO/g9k6VTCQz0VFE2v1FpxytWzUqfDtu0XL/pwnNvptzYItvGwomh1ehomRNMOyhCJKw==", + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/core": "30.1.1", - "@jest/types": "30.0.5", - "import-local": "^3.2.0", - "jest-cli": "30.1.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" } }, - "node_modules/jest-changed-files": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.0.5.tgz", - "integrity": "sha512-bGl2Ntdx0eAwXuGpdLdVYVr5YQHnSZlQ0y9HVDu565lCUAe9sj6JOtBbMmBBikGIegne9piDDIOeiLVoqTkz4A==", + "node_modules/into-stream": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-7.0.0.tgz", + "integrity": "sha512-2dYz766i9HprMBasCMvHMuazJ7u4WzhJwo5kb3iPSiW/iRYV6uPari3zHoqZlnuaR7V1bEiNMxikhp37rdBXbw==", "dev": true, "license": "MIT", "dependencies": { - "execa": "^5.1.1", - "jest-util": "30.0.5", - "p-limit": "^3.1.0" + "from2": "^2.3.0", + "p-is-promise": "^3.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-circus": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.1.1.tgz", - "integrity": "sha512-M3Vd4x5wD7eSJspuTvRF55AkOOBndRxgW3gqQBDlFvbH3X+ASdi8jc+EqXEeAFd/UHulVYIlC4XKJABOhLw6UA==", + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.1.1", - "@jest/expect": "30.1.1", - "@jest/test-result": "30.1.1", - "@jest/types": "30.0.5", - "@types/node": "*", - "chalk": "^4.1.2", - "co": "^4.6.0", - "dedent": "^1.6.0", - "is-generator-fn": "^2.1.0", - "jest-each": "30.1.0", - "jest-matcher-utils": "30.1.1", - "jest-message-util": "30.1.0", - "jest-runtime": "30.1.1", - "jest-snapshot": "30.1.1", - "jest-util": "30.0.5", - "p-limit": "^3.1.0", - "pretty-format": "30.0.5", - "pure-rand": "^7.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" + "has": "^1.0.3" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-cli": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.1.1.tgz", - "integrity": "sha512-xm9llxuh5OoI5KZaYzlMhklryHBwg9LZy/gEaaMlXlxb+cZekGNzukU0iblbDo3XOBuN6N0CgK4ykgNRYSEb6g==", + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/core": "30.1.1", - "@jest/test-result": "30.1.1", - "@jest/types": "30.0.5", - "chalk": "^4.1.2", - "exit-x": "^0.2.2", - "import-local": "^3.2.0", - "jest-config": "30.1.1", - "jest-util": "30.0.5", - "jest-validate": "30.1.0", - "yargs": "^17.7.2" - }, "bin": { - "jest": "bin/jest.js" + "is-docker": "cli.js" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-config": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.1.1.tgz", - "integrity": "sha512-xuPGUGDw+9fPPnGmddnLnHS/mhKUiJOW7K65vErYmglEPKq65NKwSRchkQ7iv6gqjs2l+YNEsAtbsplxozdOWg==", + "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", - "dependencies": { - "@babel/core": "^7.27.4", - "@jest/get-type": "30.1.0", - "@jest/pattern": "30.0.1", - "@jest/test-sequencer": "30.1.1", - "@jest/types": "30.0.5", - "babel-jest": "30.1.1", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "deepmerge": "^4.3.1", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "jest-circus": "30.1.1", - "jest-docblock": "30.0.1", - "jest-environment-node": "30.1.1", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.1.0", - "jest-runner": "30.1.1", - "jest-util": "30.0.5", - "jest-validate": "30.1.0", - "micromatch": "^4.0.8", - "parse-json": "^5.2.0", - "pretty-format": "30.0.5", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "esbuild-register": ">=3.4.0", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "esbuild-register": { - "optional": true - }, - "ts-node": { - "optional": true - } + "node": ">=0.10.0" } }, - "node_modules/jest-diff": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.1.1.tgz", - "integrity": "sha512-LUU2Gx8EhYxpdzTR6BmjL1ifgOAQJQELTHOiPv9KITaKjZvJ9Jmgigx01tuZ49id37LorpGc9dPBPlXTboXScw==", + "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": { - "@jest/diff-sequences": "30.0.1", - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "pretty-format": "30.0.5" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=8" } }, - "node_modules/jest-docblock": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.0.1.tgz", - "integrity": "sha512-/vF78qn3DYphAaIc3jy4gA7XSAz167n9Bm/wn/1XhTLW7tTBIzXtCJpb/vcmc73NIIeeohCbdL94JasyXUZsGA==", + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, "license": "MIT", - "dependencies": { - "detect-newline": "^3.1.0" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=6" } }, - "node_modules/jest-each": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.1.0.tgz", - "integrity": "sha512-A+9FKzxPluqogNahpCv04UJvcZ9B3HamqpDNWNKDjtxVRYB8xbZLFuCr8JAJFpNp83CA0anGQFlpQna9Me+/tQ==", + "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": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.0.5", - "chalk": "^4.1.2", - "jest-util": "30.0.5", - "pretty-format": "30.0.5" + "is-extglob": "^2.1.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-environment-node": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.1.1.tgz", - "integrity": "sha512-IaMoaA6saxnJimqCppUDqKck+LKM0Jg+OxyMUIvs1yGd2neiC22o8zXo90k04+tO+49OmgMR4jTgM5e4B0S62Q==", + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.1.1", - "@jest/fake-timers": "30.1.1", - "@jest/types": "30.0.5", - "@types/node": "*", - "jest-mock": "30.0.5", - "jest-util": "30.0.5", - "jest-validate": "30.1.0" + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-haste-map": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.1.0.tgz", - "integrity": "sha512-JLeM84kNjpRkggcGpQLsV7B8W4LNUWz7oDNVnY1Vjj22b5/fAb3kk3htiD+4Na8bmJmjJR7rBtS2Rmq/NEcADg==", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/types": "30.0.5", - "@types/node": "*", - "anymatch": "^3.1.3", - "fb-watchman": "^2.0.2", - "graceful-fs": "^4.2.11", - "jest-regex-util": "30.0.1", - "jest-util": "30.0.5", - "jest-worker": "30.1.0", - "micromatch": "^4.0.8", - "walker": "^1.0.8" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.3" + "node": ">=0.12.0" } }, - "node_modules/jest-leak-detector": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.1.0.tgz", - "integrity": "sha512-AoFvJzwxK+4KohH60vRuHaqXfWmeBATFZpzpmzNmYTtmRMiyGPVhkXpBqxUQunw+dQB48bDf4NpUs6ivVbRv1g==", + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "pretty-format": "30.0.5" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=8" } }, - "node_modules/jest-matcher-utils": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.1.1.tgz", - "integrity": "sha512-SuH2QVemK48BNTqReti6FtjsMPFsSOD/ZzRxU1TttR7RiRsRSe78d03bb4Cx6D4bQC/80Q8U4VnaaAH9FlbZ9w==", + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "jest-diff": "30.1.1", - "pretty-format": "30.0.5" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-message-util": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.1.0.tgz", - "integrity": "sha512-HizKDGG98cYkWmaLUHChq4iN+oCENohQLb7Z5guBPumYs+/etonmNFlg1Ps6yN9LTPyZn+M+b/9BbnHx3WTMDg==", + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@jest/types": "30.0.5", - "@types/stack-utils": "^2.0.3", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "micromatch": "^4.0.8", - "pretty-format": "30.0.5", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" + "isobject": "^3.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-mock": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.5.tgz", - "integrity": "sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ==", + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/types": "30.0.5", - "@types/node": "*", - "jest-util": "30.0.5" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">=18" }, - "peerDependencies": { - "jest-resolve": "*" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-regex-util": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", - "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", "dev": true, "license": "MIT", "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-resolve": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.1.0.tgz", - "integrity": "sha512-hASe7D/wRtZw8Cm607NrlF7fi3HWC5wmA5jCVc2QjQAB2pTwP9eVZILGEi6OeSLNUtE1zb04sXRowsdh5CUjwA==", + "node_modules/issue-parser": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-7.0.1.tgz", + "integrity": "sha512-3YZcUUR2Wt1WsapF+S/WiA2WmlW0cWAoPccMqne7AxEBhCdFeTPjfv/Axb8V2gyCgY3nRw+ksZ3xSUX+R47iAg==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.1.0", - "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.0.5", - "jest-validate": "30.1.0", - "slash": "^3.0.0", - "unrs-resolver": "^1.7.11" + "lodash.capitalize": "^4.2.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.uniqby": "^4.7.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^18.17 || >=20.6.1" } }, - "node_modules/jest-resolve-dependencies": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.1.1.tgz", - "integrity": "sha512-tRtaaoH8Ws1Gn1o/9pedt19dvVgr81WwdmvJSP9Ow3amOUOP2nN9j94u5jC9XlIfa2Q1FQKIWWQwL4ajqsjCGQ==", + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "jest-regex-util": "30.0.1", - "jest-snapshot": "30.1.1" + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=10" } }, - "node_modules/jest-runner": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.1.1.tgz", - "integrity": "sha512-ATe6372SOfJvCRExtCAr06I4rGujwFdKg44b6i7/aOgFnULwjxzugJ0Y4AnG+jeSeQi8dU7R6oqLGmsxRUbErQ==", + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "@jest/console": "30.1.1", - "@jest/environment": "30.1.1", - "@jest/test-result": "30.1.1", - "@jest/transform": "30.1.1", - "@jest/types": "30.0.5", - "@types/node": "*", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "exit-x": "^0.2.2", - "graceful-fs": "^4.2.11", - "jest-docblock": "30.0.1", - "jest-environment-node": "30.1.1", - "jest-haste-map": "30.1.0", - "jest-leak-detector": "30.1.0", - "jest-message-util": "30.1.0", - "jest-resolve": "30.1.0", - "jest-runtime": "30.1.1", - "jest-util": "30.0.5", - "jest-watcher": "30.1.1", - "jest-worker": "30.1.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=10" } }, - "node_modules/jest-runtime": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.1.1.tgz", - "integrity": "sha512-7sOyR0Oekw4OesQqqBHuYJRB52QtXiq0NNgLRzVogiMSxKCMiliUd6RrXHCnG5f12Age/ggidCBiQftzcA9XKw==", + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "@jest/environment": "30.1.1", - "@jest/fake-timers": "30.1.1", - "@jest/globals": "30.1.1", - "@jest/source-map": "30.0.1", - "@jest/test-result": "30.1.1", - "@jest/transform": "30.1.1", - "@jest/types": "30.0.5", - "@types/node": "*", - "chalk": "^4.1.2", - "cjs-module-lexer": "^2.1.0", - "collect-v8-coverage": "^1.0.2", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.1.0", - "jest-message-util": "30.1.0", - "jest-mock": "30.0.5", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.1.0", - "jest-snapshot": "30.1.1", - "jest-util": "30.0.5", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=10" } }, - "node_modules/jest-snapshot": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.1.1.tgz", - "integrity": "sha512-7/iBEzoJqEt2TjkQY+mPLHP8cbPhLReZVkkxjTMzIzoTC4cZufg7HzKo/n9cIkXKj2LG0x3mmBHsZto+7TOmFg==", + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "@babel/core": "^7.27.4", - "@babel/generator": "^7.27.5", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1", - "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.1.1", - "@jest/get-type": "30.1.0", - "@jest/snapshot-utils": "30.1.1", - "@jest/transform": "30.1.1", - "@jest/types": "30.0.5", - "babel-preset-current-node-syntax": "^1.1.0", - "chalk": "^4.1.2", - "expect": "30.1.1", - "graceful-fs": "^4.2.11", - "jest-diff": "30.1.1", - "jest-matcher-utils": "30.1.1", - "jest-message-util": "30.1.0", - "jest-util": "30.0.5", - "pretty-format": "30.0.5", - "semver": "^7.7.2", - "synckit": "^0.11.8" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=8" } }, - "node_modules/jest-util": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.5.tgz", - "integrity": "sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==", + "node_modules/istextorbinary": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-9.5.0.tgz", + "integrity": "sha512-5mbUj3SiZXCuRf9fT3ibzbSSEWiy63gFfksmGfdOzujPjW3k+z8WvIBxcJHBoQNlaZaiyB25deviif2+osLmLw==", "dev": true, - "license": "MIT", + "license": "Artistic-2.0", "dependencies": { - "@jest/types": "30.0.5", - "@types/node": "*", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "graceful-fs": "^4.2.11", - "picomatch": "^4.0.2" + "binaryextensions": "^6.11.0", + "editions": "^6.21.0", + "textextensions": "^6.11.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" } }, - "node_modules/jest-util/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "node_modules/jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^9.0.0" + }, "engines": { - "node": ">=12" + "node": "20 || >=22" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/jest-validate": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.1.0.tgz", - "integrity": "sha512-7P3ZlCFW/vhfQ8pE7zW6Oi4EzvuB4sgR72Q1INfW9m0FGo0GADYlPwIkf4CyPq7wq85g+kPMtPOHNAdWHeBOaA==", + "node_modules/java-properties": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz", + "integrity": "sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/jest": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.3.0.tgz", + "integrity": "sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.0.5", - "camelcase": "^6.3.0", - "chalk": "^4.1.2", - "leven": "^3.1.0", - "pretty-format": "30.0.5" + "@jest/core": "30.3.0", + "@jest/types": "30.3.0", + "import-local": "^3.2.0", + "jest-cli": "30.3.0" + }, + "bin": { + "jest": "bin/jest.js" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "node_modules/jest-changed-files": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.3.0.tgz", + "integrity": "sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=10" + "dependencies": { + "execa": "^5.1.1", + "jest-util": "30.3.0", + "p-limit": "^3.1.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-watcher": { - "version": "30.1.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.1.1.tgz", - "integrity": "sha512-CrAQ73LlaS6KGQQw6NBi71g7qvP7scy+4+2c0jKX6+CWaYg85lZiig5nQQVTsS5a5sffNPL3uxXnaE9d7v9eQg==", + "node_modules/jest-circus": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.3.0.tgz", + "integrity": "sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.1.1", - "@jest/types": "30.0.5", + "@jest/environment": "30.3.0", + "@jest/expect": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", - "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", - "emittery": "^0.13.1", - "jest-util": "30.0.5", - "string-length": "^4.0.2" + "co": "^4.6.0", + "dedent": "^1.6.0", + "is-generator-fn": "^2.1.0", + "jest-each": "30.3.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-runtime": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", + "p-limit": "^3.1.0", + "pretty-format": "30.3.0", + "pure-rand": "^7.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-worker": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.1.0.tgz", - "integrity": "sha512-uvWcSjlwAAgIu133Tt77A05H7RIk3Ho8tZL50bQM2AkvLdluw9NG48lRCl3Dt+MOH719n/0nnb5YxUwcuJiKRA==", + "node_modules/jest-cli": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.3.0.tgz", + "integrity": "sha512-l6Tqx+j1fDXJEW5bqYykDQQ7mQg+9mhWXtnj+tQZrTWYHyHoi6Be8HPumDSA+UiX2/2buEgjA58iJzdj146uCw==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*", - "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.0.5", - "merge-stream": "^2.0.0", - "supports-color": "^8.1.1" + "@jest/core": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", + "chalk": "^4.1.2", + "exit-x": "^0.2.2", + "import-local": "^3.2.0", + "jest-config": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "yargs": "^17.7.2" + }, + "bin": { + "jest": "bin/jest.js" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/jest-config": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.3.0.tgz", + "integrity": "sha512-WPMAkMAtNDY9P/oKObtsRG/6KTrhtgPJoBTmk20uDn4Uy6/3EJnnaZJre/FMT1KVRx8cve1r7/FlMIOfRVWL4w==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@babel/core": "^7.27.4", + "@jest/get-type": "30.1.0", + "@jest/pattern": "30.0.1", + "@jest/test-sequencer": "30.3.0", + "@jest/types": "30.3.0", + "babel-jest": "30.3.0", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "deepmerge": "^4.3.1", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "jest-circus": "30.3.0", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.3.0", + "jest-runner": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "parse-json": "^5.2.0", + "pretty-format": "30.3.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": ">=10" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "peerDependencies": { + "@types/node": "*", + "esbuild-register": ">=3.4.0", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "esbuild-register": { + "optional": true + }, + "ts-node": { + "optional": true + } } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "node_modules/jest-config/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "engines": { - "node": ">=6" + "node": ">=12" } }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "node_modules/jest-config/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "node_modules/jest-config/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } }, - "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==", + "node_modules/jest-config/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/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true, - "license": "ISC" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "node_modules/jest-config/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, - "license": "MIT", + "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": { - "json5": "lib/cli.js" + "glob": "dist/esm/bin.mjs" }, - "engines": { - "node": ">=6" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "node_modules/jest-config/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "dependencies": { - "universalify": "^2.0.0" + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" }, "optionalDependencies": { - "graceful-fs": "^4.1.6" + "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "node_modules/jest-config/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, - "engines": [ - "node >= 0.2.0" - ], - "license": "MIT" + "license": "ISC" }, - "node_modules/JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "node_modules/jest-config/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, - "license": "(MIT OR Apache-2.0)", + "license": "ISC", "dependencies": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - }, - "bin": { - "JSONStream": "bin.js" + "brace-expansion": "^2.0.2" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/keytar": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz", - "integrity": "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ==", + "node_modules/jest-config/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, - "hasInstallScript": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "dependencies": { - "node-addon-api": "^4.3.0", - "prebuild-install": "^7.0.1" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "node_modules/jest-config/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "node_modules/jest-config/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, "engines": { - "node": ">=6" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/levenshtein": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/levenshtein/-/levenshtein-1.0.5.tgz", - "integrity": "sha1-ORFzepy1baNF0Aj1V4LG8TiXm6M= sha512-UQf1nnmxjl7O0+snDXj2YF2r74Gkya8ZpnegrUBYN9tikh2dtxV/ey8e07BO5wwo0i76yjOvbDhFHdcPEiH9aA==", - "dev": true, - "engines": [ - "node >=0.2.0" - ], - "license": "Public Domain" - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "node_modules/jest-diff": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz", + "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==", "dev": true, "license": "MIT", "dependencies": { - "uc.micro": "^1.0.1" + "@jest/diff-sequences": "30.3.0", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "node_modules/jest-docblock": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", + "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", "dev": true, "license": "MIT", "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" + "detect-newline": "^3.1.0" }, "engines": { - "node": ">=4" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/load-json-file/node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "node_modules/jest-each": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.3.0.tgz", + "integrity": "sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA==", "dev": true, "license": "MIT", "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "@jest/get-type": "30.1.0", + "@jest/types": "30.3.0", + "chalk": "^4.1.2", + "jest-util": "30.3.0", + "pretty-format": "30.3.0" }, "engines": { - "node": ">=4" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/load-json-file/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "node_modules/jest-environment-node": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.3.0.tgz", + "integrity": "sha512-4i6HItw/JSiJVsC5q0hnKIe/hbYfZLVG9YJ/0pU9Hz2n/9qZe3Rhn5s5CUZA5ORZlcdT/vmAXRMyONXJwPrmYQ==", "dev": true, "license": "MIT", + "dependencies": { + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-mock": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0" + }, "engines": { - "node": ">=4" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/loader-runner": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", - "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", + "node_modules/jest-haste-map": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz", + "integrity": "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==", "dev": true, "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", + "picomatch": "^4.0.3", + "walker": "^1.0.8" + }, "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.capitalize": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", - "integrity": "sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.escaperegexp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", - "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.ismatch": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", - "integrity": "sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "dev": true, - "license": "MIT" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.3" + } }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "node_modules/jest-haste-map/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, - "node_modules/lodash.uniqby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", - "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==", + "node_modules/jest-leak-detector": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.3.0.tgz", + "integrity": "sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "node_modules/jest-matcher-utils": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz", + "integrity": "sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "yallist": "^3.0.2" + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.3.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "node_modules/jest-message-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", + "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", "dev": true, "license": "MIT", "dependencies": { - "semver": "^7.5.3" + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.3.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3", + "pretty-format": "30.3.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "node_modules/jest-message-util/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, - "license": "ISC" + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "node_modules/jest-mock": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz", + "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "tmpl": "1.0.5" + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-util": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/map-obj": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", - "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=6" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/markdown-it": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", - "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "node_modules/jest-resolve": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.3.0.tgz", + "integrity": "sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g==", "dev": true, "license": "MIT", "dependencies": { - "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "jest-pnp-resolver": "^1.2.3", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "slash": "^3.0.0", + "unrs-resolver": "^1.7.11" }, - "bin": { - "markdown-it": "bin/markdown-it.js" + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/markdown-it/node_modules/entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "node_modules/jest-resolve-dependencies": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.3.0.tgz", + "integrity": "sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw==", "dev": true, - "license": "BSD-2-Clause", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "license": "MIT", + "dependencies": { + "jest-regex-util": "30.0.1", + "jest-snapshot": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/marked": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.5.tgz", - "integrity": "sha512-jPueVhumq7idETHkb203WDD4fMA3yV9emQ5vLwop58lu8bTclMghBWcYAavlDqIEMaisADinV1TooIFCfqOsYQ==", + "node_modules/jest-runner": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.3.0.tgz", + "integrity": "sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw==", "dev": true, "license": "MIT", - "peer": true, - "bin": { - "marked": "bin/marked.js" + "dependencies": { + "@jest/console": "30.3.0", + "@jest/environment": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.3.0", + "jest-haste-map": "30.3.0", + "jest-leak-detector": "30.3.0", + "jest-message-util": "30.3.0", + "jest-resolve": "30.3.0", + "jest-runtime": "30.3.0", + "jest-util": "30.3.0", + "jest-watcher": "30.3.0", + "jest-worker": "30.3.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" }, "engines": { - "node": ">= 12" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/marked-terminal": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-5.1.1.tgz", - "integrity": "sha512-+cKTOx9P4l7HwINYhzbrBSyzgxO2HaHKGZGuB1orZsMIgXYaJyfidT81VXRdpelW/PcHEWxywscePVgI/oUF6g==", + "node_modules/jest-runtime": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.3.0.tgz", + "integrity": "sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng==", "dev": true, "license": "MIT", "dependencies": { - "ansi-escapes": "^5.0.0", - "cardinal": "^2.1.1", - "chalk": "^5.0.0", - "cli-table3": "^0.6.1", - "node-emoji": "^1.11.0", - "supports-hyperlinks": "^2.2.0" + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/globals": "30.3.0", + "@jest/source-map": "30.0.1", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "cjs-module-lexer": "^2.1.0", + "collect-v8-coverage": "^1.0.2", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" }, "engines": { - "node": ">=14.13.1 || >=16.0.0" - }, - "peerDependencies": { - "marked": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/marked-terminal/node_modules/ansi-escapes": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", - "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", + "node_modules/jest-runtime/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "type-fest": "^1.0.2" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-runtime/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/marked-terminal/node_modules/chalk": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", - "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", + "node_modules/jest-runtime/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", "dev": true, "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/jest-runtime/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/jest-runtime/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "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/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/marked-terminal/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "node_modules/jest-runtime/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "node_modules/jest-runtime/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, - "license": "MIT" + "license": "ISC" }, - "node_modules/meow": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", - "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", + "node_modules/jest-runtime/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" + "brace-expansion": "^2.0.2" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-runtime/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/meow/node_modules/type-fest": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", - "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "node_modules/jest-runtime/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, - "license": "(MIT OR CC0-1.0)", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "node_modules/jest-runtime/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, "engines": { - "node": ">= 8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "node_modules/jest-snapshot": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.3.0.tgz", + "integrity": "sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ==", "dev": true, "license": "MIT", "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" + "@babel/core": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1", + "@babel/types": "^7.27.3", + "@jest/expect-utils": "30.3.0", + "@jest/get-type": "30.1.0", + "@jest/snapshot-utils": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "babel-preset-current-node-syntax": "^1.2.0", + "chalk": "^4.1.2", + "expect": "30.3.0", + "graceful-fs": "^4.2.11", + "jest-diff": "30.3.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "pretty-format": "30.3.0", + "semver": "^7.7.2", + "synckit": "^0.11.8" }, "engines": { - "node": ">=8.6" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "node_modules/jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", "dev": true, "license": "MIT", - "bin": { - "mime": "cli.js" + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" }, "engines": { - "node": ">=10.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/mime-db": { - "version": "1.50.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz", - "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==", + "node_modules/jest-util/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/mime-types": { - "version": "2.1.33", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.33.tgz", - "integrity": "sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==", + "node_modules/jest-validate": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz", + "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==", "dev": true, "license": "MIT", "dependencies": { - "mime-db": "1.50.0" + "@jest/get-type": "30.1.0", + "@jest/types": "30.3.0", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.3.0" }, "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "node_modules/jest-validate/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": { @@ -6507,2633 +8152,2541 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "node_modules/jest-watcher": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.3.0.tgz", + "integrity": "sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w==", "dev": true, "license": "MIT", + "dependencies": { + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "jest-util": "30.3.0", + "string-length": "^4.0.2" + }, "engines": { - "node": ">=4" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "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/jest-worker": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.3.0.tgz", + "integrity": "sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "@types/node": "*", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.3.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.1.1" }, "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", "dependencies": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" + "has-flag": "^4.0.0" }, "engines": { - "node": ">= 6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } + "license": "MIT" }, - "node_modules/mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==", - "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { - "minimist": "0.0.8" + "argparse": "^2.0.1" }, "bin": { - "mkdirp": "bin/cmd.js" + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true, "license": "MIT" }, - "node_modules/mkdirp/node_modules/minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==", + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true, "license": "MIT" }, - "node_modules/mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-with-bigint": { + "version": "3.5.8", + "resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.8.tgz", + "integrity": "sha512-eq/4KP6K34kwa7TcFdtvnftvHCD9KvHOGGICWwMFc4dOOKF5t4iYqnfLK8otCRCRv06FXOzGGyqE8h8ElMvvdw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "license": "MIT", - "dependencies": { - "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.5", - "he": "1.1.1", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "supports-color": "5.4.0" - }, "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" + "json5": "lib/cli.js" }, "engines": { - "node": ">= 4.0.0" + "node": ">=6" } }, - "node_modules/mocha/node_modules/commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", "dev": true, "license": "MIT" }, - "node_modules/mocha/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, "license": "MIT", "dependencies": { - "ms": "2.0.0" + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/mocha/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", "dev": true, "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/mocha/node_modules/glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" }, "engines": { - "node": "*" + "node": ">=12", + "npm": ">=6" } }, - "node_modules/mocha/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0= sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=4" + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" } }, - "node_modules/mocha/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" } }, - "node_modules/mocha/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "node_modules/keytar": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz", + "integrity": "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ==", "dev": true, + "hasInstallScript": true, "license": "MIT", + "optional": true, "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" + "node-addon-api": "^4.3.0", + "prebuild-install": "^7.0.1" } }, - "node_modules/modify-values": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", - "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=6" + } }, - "node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "node_modules/levenshtein": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/levenshtein/-/levenshtein-1.0.5.tgz", + "integrity": "sha512-UQf1nnmxjl7O0+snDXj2YF2r74Gkya8ZpnegrUBYN9tikh2dtxV/ey8e07BO5wwo0i76yjOvbDhFHdcPEiH9aA==", "dev": true, - "license": "ISC" + "engines": [ + "node >=0.2.0" + ], + "license": "Public Domain" }, - "node_modules/napi-build-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", - "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true, "license": "MIT" }, - "node_modules/napi-postinstall": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.3.tgz", - "integrity": "sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==", + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", "dev": true, "license": "MIT", - "bin": { - "napi-postinstall": "lib/cli.js" + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" }, "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/napi-postinstall" + "node": ">=4" } }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "license": "MIT" - }, - "node_modules/nerf-dart": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz", - "integrity": "sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-abi": { - "version": "3.30.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.30.0.tgz", - "integrity": "sha512-qWO5l3SCqbwQavymOmtTVuCWZE23++S+rxyoHjXqUmPyzRcaoI4lA2gO55/drddGnedAyjA7sk76SfQ5lfUMnw==", + "node_modules/load-json-file/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", "dev": true, "license": "MIT", "dependencies": { - "semver": "^7.3.5" + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" }, "engines": { - "node": ">=10" + "node": ">=4" } }, - "node_modules/node-addon-api": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", - "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", + "node_modules/load-json-file/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=4" + } }, - "node_modules/node-emoji": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", - "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", + "node_modules/loader-runner": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "dev": true, "license": "MIT", - "dependencies": { - "lodash": "^4.17.21" + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", "dependencies": { - "whatwg-url": "^5.0.0" + "p-locate": "^4.1.0" }, "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "node": ">=8" } }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "dev": true, "license": "MIT" }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", "dev": true, "license": "MIT" }, - "node_modules/normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "node_modules/lodash.capitalize": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", + "integrity": "sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } + "license": "MIT" }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "license": "MIT" }, - "node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "license": "MIT" }, - "node_modules/npm": { - "version": "8.19.3", - "resolved": "https://registry.npmjs.org/npm/-/npm-8.19.3.tgz", - "integrity": "sha512-0QjmyPtDxSyMWWD8I91QGbrgx9KzbV6C9FK1liEb/K0zppiZkr5KxXc990G+LzPwBHDfRjUBlO9T1qZ08vl9mA==", - "bundleDependencies": [ - "@isaacs/string-locale-compare", - "@npmcli/arborist", - "@npmcli/ci-detect", - "@npmcli/config", - "@npmcli/fs", - "@npmcli/map-workspaces", - "@npmcli/package-json", - "@npmcli/run-script", - "abbrev", - "archy", - "cacache", - "chalk", - "chownr", - "cli-columns", - "cli-table3", - "columnify", - "fastest-levenshtein", - "fs-minipass", - "glob", - "graceful-fs", - "hosted-git-info", - "ini", - "init-package-json", - "is-cidr", - "json-parse-even-better-errors", - "libnpmaccess", - "libnpmdiff", - "libnpmexec", - "libnpmfund", - "libnpmhook", - "libnpmorg", - "libnpmpack", - "libnpmpublish", - "libnpmsearch", - "libnpmteam", - "libnpmversion", - "make-fetch-happen", - "minimatch", - "minipass", - "minipass-pipeline", - "mkdirp", - "mkdirp-infer-owner", - "ms", - "node-gyp", - "nopt", - "npm-audit-report", - "npm-install-checks", - "npm-package-arg", - "npm-pick-manifest", - "npm-profile", - "npm-registry-fetch", - "npm-user-validate", - "npmlog", - "opener", - "p-map", - "pacote", - "parse-conflict-json", - "proc-log", - "qrcode-terminal", - "read", - "read-package-json", - "read-package-json-fast", - "readdir-scoped-modules", - "rimraf", - "semver", - "ssri", - "tar", - "text-table", - "tiny-relative-date", - "treeverse", - "validate-npm-package-name", - "which", - "write-file-atomic" - ], + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", "dev": true, - "license": "Artistic-2.0", - "workspaces": [ - "docs", - "smoke-tests", - "workspaces/*" - ], - "dependencies": { - "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^5.6.3", - "@npmcli/ci-detect": "^2.0.0", - "@npmcli/config": "^4.2.1", - "@npmcli/fs": "^2.1.0", - "@npmcli/map-workspaces": "^2.0.3", - "@npmcli/package-json": "^2.0.0", - "@npmcli/run-script": "^4.2.1", - "abbrev": "~1.1.1", - "archy": "~1.0.0", - "cacache": "^16.1.3", - "chalk": "^4.1.2", - "chownr": "^2.0.0", - "cli-columns": "^4.0.0", - "cli-table3": "^0.6.2", - "columnify": "^1.6.0", - "fastest-levenshtein": "^1.0.12", - "fs-minipass": "^2.1.0", - "glob": "^8.0.1", - "graceful-fs": "^4.2.10", - "hosted-git-info": "^5.2.1", - "ini": "^3.0.1", - "init-package-json": "^3.0.2", - "is-cidr": "^4.0.2", - "json-parse-even-better-errors": "^2.3.1", - "libnpmaccess": "^6.0.4", - "libnpmdiff": "^4.0.5", - "libnpmexec": "^4.0.14", - "libnpmfund": "^3.0.5", - "libnpmhook": "^8.0.4", - "libnpmorg": "^4.0.4", - "libnpmpack": "^4.1.3", - "libnpmpublish": "^6.0.5", - "libnpmsearch": "^5.0.4", - "libnpmteam": "^4.0.4", - "libnpmversion": "^3.0.7", - "make-fetch-happen": "^10.2.0", - "minimatch": "^5.1.0", - "minipass": "^3.1.6", - "minipass-pipeline": "^1.2.4", - "mkdirp": "^1.0.4", - "mkdirp-infer-owner": "^2.0.0", - "ms": "^2.1.2", - "node-gyp": "^9.1.0", - "nopt": "^6.0.0", - "npm-audit-report": "^3.0.0", - "npm-install-checks": "^5.0.0", - "npm-package-arg": "^9.1.0", - "npm-pick-manifest": "^7.0.2", - "npm-profile": "^6.2.0", - "npm-registry-fetch": "^13.3.1", - "npm-user-validate": "^1.0.1", - "npmlog": "^6.0.2", - "opener": "^1.5.2", - "p-map": "^4.0.0", - "pacote": "^13.6.2", - "parse-conflict-json": "^2.0.2", - "proc-log": "^2.0.1", - "qrcode-terminal": "^0.12.0", - "read": "~1.0.7", - "read-package-json": "^5.0.2", - "read-package-json-fast": "^2.0.3", - "readdir-scoped-modules": "^1.1.0", - "rimraf": "^3.0.2", - "semver": "^7.3.7", - "ssri": "^9.0.1", - "tar": "^6.1.11", - "text-table": "~0.2.0", - "tiny-relative-date": "^1.3.0", - "treeverse": "^2.0.0", - "validate-npm-package-name": "^4.0.0", - "which": "^2.0.2", - "write-file-atomic": "^4.0.1" - }, - "bin": { - "npm": "bin/npm-cli.js", - "npx": "bin/npx-cli.js" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } + "license": "MIT" }, - "node_modules/npm-run-path": { + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isstring": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/npm/node_modules/@colors/colors": { - "version": "1.5.0", + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.1.90" - } + "license": "MIT" }, - "node_modules/npm/node_modules/@gar/promisify": { - "version": "1.1.3", + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "dev": true, - "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/@isaacs/string-locale-compare": { - "version": "1.1.0", + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", "dev": true, - "inBundle": true, - "license": "ISC" + "license": "MIT" }, - "node_modules/npm/node_modules/@npmcli/arborist": { - "version": "5.6.3", + "node_modules/lodash.uniqby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", + "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, - "inBundle": true, "license": "ISC", "dependencies": { - "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/installed-package-contents": "^1.0.7", - "@npmcli/map-workspaces": "^2.0.3", - "@npmcli/metavuln-calculator": "^3.0.1", - "@npmcli/move-file": "^2.0.0", - "@npmcli/name-from-folder": "^1.0.1", - "@npmcli/node-gyp": "^2.0.0", - "@npmcli/package-json": "^2.0.0", - "@npmcli/query": "^1.2.0", - "@npmcli/run-script": "^4.1.3", - "bin-links": "^3.0.3", - "cacache": "^16.1.3", - "common-ancestor-path": "^1.0.1", - "hosted-git-info": "^5.2.1", - "json-parse-even-better-errors": "^2.3.1", - "json-stringify-nice": "^1.1.4", - "minimatch": "^5.1.0", - "mkdirp": "^1.0.4", - "mkdirp-infer-owner": "^2.0.0", - "nopt": "^6.0.0", - "npm-install-checks": "^5.0.0", - "npm-package-arg": "^9.0.0", - "npm-pick-manifest": "^7.0.2", - "npm-registry-fetch": "^13.0.0", - "npmlog": "^6.0.2", - "pacote": "^13.6.1", - "parse-conflict-json": "^2.0.1", - "proc-log": "^2.0.0", - "promise-all-reject-late": "^1.0.0", - "promise-call-limit": "^1.0.1", - "read-package-json-fast": "^2.0.2", - "readdir-scoped-modules": "^1.1.0", - "rimraf": "^3.0.2", - "semver": "^7.3.7", - "ssri": "^9.0.0", - "treeverse": "^2.0.0", - "walk-up-path": "^1.0.0" - }, - "bin": { - "arborist": "bin/index.js" + "yallist": "^3.0.2" + } + }, + "node_modules/make-asynchronous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/make-asynchronous/-/make-asynchronous-1.1.0.tgz", + "integrity": "sha512-ayF7iT+44LXdxJLTrTd3TLQpFDDvPCBxXxbv+pMUSuHA5Q8zyAfwkRP6aHHwNVFBUFWtxAHqwNJxF8vMZLAbVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-event": "^6.0.0", + "type-fest": "^4.6.0", + "web-worker": "^1.5.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/@npmcli/ci-detect": { - "version": "2.0.0", + "node_modules/make-asynchronous/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "(MIT OR CC0-1.0)", "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/@npmcli/config": { - "version": "4.2.2", + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "@npmcli/map-workspaces": "^2.0.2", - "ini": "^3.0.0", - "mkdirp-infer-owner": "^2.0.0", - "nopt": "^6.0.0", - "proc-log": "^2.0.0", - "read-package-json-fast": "^2.0.3", - "semver": "^7.3.5", - "walk-up-path": "^1.0.0" + "semver": "^7.5.3" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/@npmcli/disparity-colors": { - "version": "2.0.0", + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "ansi-styles": "^4.3.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "tmpl": "1.0.5" } }, - "node_modules/npm/node_modules/@npmcli/fs": { - "version": "2.1.2", + "node_modules/marked": { + "version": "15.0.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@gar/promisify": "^1.1.3", - "semver": "^7.3.5" + "license": "MIT", + "peer": true, + "bin": { + "marked": "bin/marked.js" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">= 18" } }, - "node_modules/npm/node_modules/@npmcli/git": { - "version": "3.0.2", + "node_modules/marked-terminal": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.3.0.tgz", + "integrity": "sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "@npmcli/promise-spawn": "^3.0.0", - "lru-cache": "^7.4.4", - "mkdirp": "^1.0.4", - "npm-pick-manifest": "^7.0.0", - "proc-log": "^2.0.0", - "promise-inflight": "^1.0.1", - "promise-retry": "^2.0.1", - "semver": "^7.3.5", - "which": "^2.0.2" + "ansi-escapes": "^7.0.0", + "ansi-regex": "^6.1.0", + "chalk": "^5.4.1", + "cli-highlight": "^2.1.11", + "cli-table3": "^0.6.5", + "node-emoji": "^2.2.0", + "supports-hyperlinks": "^3.1.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "marked": ">=1 <16" } }, - "node_modules/npm/node_modules/@npmcli/installed-package-contents": { - "version": "1.0.7", + "node_modules/marked-terminal/node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "npm-bundled": "^1.1.1", - "npm-normalize-package-bin": "^1.0.1" - }, - "bin": { - "installed-package-contents": "index.js" + "environment": "^1.0.0" }, "engines": { - "node": ">= 10" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/@npmcli/installed-package-contents/node_modules/npm-bundled": { - "version": "1.1.2", + "node_modules/marked-terminal/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-normalize-package-bin": "^1.0.1" + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/npm/node_modules/@npmcli/map-workspaces": { - "version": "2.0.4", + "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, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/name-from-folder": "^1.0.1", - "glob": "^8.0.1", - "minimatch": "^5.0.1", - "read-package-json-fast": "^2.0.3" - }, + "license": "MIT", "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">= 0.4" } }, - "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { - "version": "3.1.1", + "node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "cacache": "^16.0.0", - "json-parse-even-better-errors": "^2.3.1", - "pacote": "^13.0.3", - "semver": "^7.3.5" + "license": "MIT", + "engines": { + "node": ">=18" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "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": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">= 8" } }, - "node_modules/npm/node_modules/@npmcli/move-file": { - "version": "2.0.1", + "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, - "inBundle": true, "license": "MIT", "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" + "braces": "^3.0.3", + "picomatch": "^2.3.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=8.6" } }, - "node_modules/npm/node_modules/@npmcli/name-from-folder": { - "version": "1.0.1", + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true, - "inBundle": true, - "license": "ISC" + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } }, - "node_modules/npm/node_modules/@npmcli/node-gyp": { - "version": "2.0.0", + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">= 0.6" } }, - "node_modules/npm/node_modules/@npmcli/package-json": { - "version": "2.0.0", + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "json-parse-even-better-errors": "^2.3.1" + "mime-db": "1.52.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/@npmcli/promise-spawn": { - "version": "3.0.0", + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, - "inBundle": true, "license": "ISC", "dependencies": { - "infer-owner": "^1.0.4" + "brace-expansion": "^1.1.7" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "*" } }, - "node_modules/npm/node_modules/@npmcli/query": { - "version": "1.2.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-package-arg": "^9.1.0", - "postcss-selector-parser": "^6.0.10", - "semver": "^7.3.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node_modules/minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/npm/node_modules/@npmcli/run-script": { - "version": "4.2.1", + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/node-gyp": "^2.0.0", - "@npmcli/promise-spawn": "^3.0.0", - "node-gyp": "^9.0.0", - "read-package-json-fast": "^2.0.3", - "which": "^2.0.2" - }, + "license": "BlueOak-1.0.0", "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/npm/node_modules/@tootallnate/once": { - "version": "2.0.0", + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "dev": true, - "inBundle": true, "license": "MIT", - "engines": { - "node": ">= 10" - } + "optional": true }, - "node_modules/npm/node_modules/abbrev": { - "version": "1.1.1", + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true, - "inBundle": true, "license": "ISC" }, - "node_modules/npm/node_modules/agent-base": { - "version": "6.0.2", + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" } }, - "node_modules/npm/node_modules/agentkeepalive": { - "version": "4.2.1", + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", "dev": true, - "inBundle": true, "license": "MIT", - "dependencies": { - "debug": "^4.1.0", - "depd": "^1.1.2", - "humanize-ms": "^1.2.1" + "optional": true + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" }, "engines": { - "node": ">= 8.0.0" + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" } }, - "node_modules/npm/node_modules/aggregate-error": { - "version": "3.1.0", + "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/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, + "node_modules/nerf-dart": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz", + "integrity": "sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-abi": { + "version": "3.30.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.30.0.tgz", + "integrity": "sha512-qWO5l3SCqbwQavymOmtTVuCWZE23++S+rxyoHjXqUmPyzRcaoI4lA2gO55/drddGnedAyjA7sk76SfQ5lfUMnw==", "dev": true, - "inBundle": true, "license": "MIT", + "optional": true, "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" + "semver": "^7.3.5" }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/npm/node_modules/ansi-regex": { - "version": "5.0.1", + "node_modules/node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", "dev": true, - "inBundle": true, "license": "MIT", - "engines": { - "node": ">=8" - } + "optional": true }, - "node_modules/npm/node_modules/ansi-styles": { - "version": "4.3.0", + "node_modules/node-emoji": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", + "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "@sindresorhus/is": "^4.6.0", + "char-regex": "^1.0.2", + "emojilib": "^2.4.0", + "skin-tone": "^2.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=18" } }, - "node_modules/npm/node_modules/aproba": { - "version": "2.0.0", + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", "dev": true, - "inBundle": true, - "license": "ISC" + "license": "MIT" }, - "node_modules/npm/node_modules/archy": { - "version": "1.0.0", + "node_modules/node-releases": { + "version": "2.0.37", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", "dev": true, - "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/are-we-there-yet": { - "version": "3.0.1", + "node_modules/node-sarif-builder": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/node-sarif-builder/-/node-sarif-builder-3.4.0.tgz", + "integrity": "sha512-tGnJW6OKRii9u/b2WiUViTJS+h7Apxx17qsMUjsUeNDiMMX5ZFf8F8Fcz7PAQ6omvOxHZtvDTmOYKJQwmfpjeg==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" + "@types/sarif": "^2.1.7", + "fs-extra": "^11.1.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=20" } }, - "node_modules/npm/node_modules/asap": { - "version": "2.0.6", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/balanced-match": { - "version": "1.0.2", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/bin-links": { - "version": "3.0.3", + "node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "BSD-2-Clause", "dependencies": { - "cmd-shim": "^5.0.0", - "mkdirp-infer-owner": "^2.0.0", - "npm-normalize-package-bin": "^2.0.0", - "read-cmd-shim": "^3.0.0", - "rimraf": "^3.0.0", - "write-file-atomic": "^4.0.0" + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/bin-links/node_modules/npm-normalize-package-bin": { - "version": "2.0.0", + "node_modules/normalize-package-data/node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", "dev": true, - "inBundle": true, "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/binary-extensions": { - "version": "2.2.0", + "node_modules/normalize-package-data/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "license": "ISC" }, - "node_modules/npm/node_modules/brace-expansion": { - "version": "2.0.1", + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, - "inBundle": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/npm/node_modules/builtins": { - "version": "5.0.1", + "node_modules/normalize-url": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-9.0.0.tgz", + "integrity": "sha512-z9nC87iaZXXySbWWtTHfCFJyFvKaUAW6lODhikG7ILSbVgmwuFjUqkgnheHvAUcGedO29e2QGBRXMUD64aurqQ==", "dev": true, - "inBundle": true, "license": "MIT", - "dependencies": { - "semver": "^7.0.0" + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/cacache": { - "version": "16.1.3", + "node_modules/npm": { + "version": "11.12.1", + "resolved": "https://registry.npmjs.org/npm/-/npm-11.12.1.tgz", + "integrity": "sha512-zcoUuF1kezGSAo0CqtvoLXX3mkRqzuqYdL6Y5tdo8g69NVV3CkjQ6ZBhBgB4d7vGkPcV6TcvLi3GRKPDFX+xTA==", + "bundleDependencies": [ + "@isaacs/string-locale-compare", + "@npmcli/arborist", + "@npmcli/config", + "@npmcli/fs", + "@npmcli/map-workspaces", + "@npmcli/metavuln-calculator", + "@npmcli/package-json", + "@npmcli/promise-spawn", + "@npmcli/redact", + "@npmcli/run-script", + "@sigstore/tuf", + "abbrev", + "archy", + "cacache", + "chalk", + "ci-info", + "fastest-levenshtein", + "fs-minipass", + "glob", + "graceful-fs", + "hosted-git-info", + "ini", + "init-package-json", + "is-cidr", + "json-parse-even-better-errors", + "libnpmaccess", + "libnpmdiff", + "libnpmexec", + "libnpmfund", + "libnpmorg", + "libnpmpack", + "libnpmpublish", + "libnpmsearch", + "libnpmteam", + "libnpmversion", + "make-fetch-happen", + "minimatch", + "minipass", + "minipass-pipeline", + "ms", + "node-gyp", + "nopt", + "npm-audit-report", + "npm-install-checks", + "npm-package-arg", + "npm-pick-manifest", + "npm-profile", + "npm-registry-fetch", + "npm-user-validate", + "p-map", + "pacote", + "parse-conflict-json", + "proc-log", + "qrcode-terminal", + "read", + "semver", + "spdx-expression-parse", + "ssri", + "supports-color", + "tar", + "text-table", + "tiny-relative-date", + "treeverse", + "validate-npm-package-name", + "which" + ], "dev": true, - "inBundle": true, - "license": "ISC", + "license": "Artistic-2.0", + "workspaces": [ + "docs", + "smoke-tests", + "mock-globals", + "mock-registry", + "workspaces/*" + ], "dependencies": { - "@npmcli/fs": "^2.1.0", - "@npmcli/move-file": "^2.0.0", - "chownr": "^2.0.0", - "fs-minipass": "^2.1.0", - "glob": "^8.0.1", - "infer-owner": "^1.0.4", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^9.4.2", + "@npmcli/config": "^10.8.1", + "@npmcli/fs": "^5.0.0", + "@npmcli/map-workspaces": "^5.0.3", + "@npmcli/metavuln-calculator": "^9.0.3", + "@npmcli/package-json": "^7.0.5", + "@npmcli/promise-spawn": "^9.0.1", + "@npmcli/redact": "^4.0.0", + "@npmcli/run-script": "^10.0.4", + "@sigstore/tuf": "^4.0.2", + "abbrev": "^4.0.0", + "archy": "~1.0.0", + "cacache": "^20.0.4", + "chalk": "^5.6.2", + "ci-info": "^4.4.0", + "fastest-levenshtein": "^1.0.16", + "fs-minipass": "^3.0.3", + "glob": "^13.0.6", + "graceful-fs": "^4.2.11", + "hosted-git-info": "^9.0.2", + "ini": "^6.0.0", + "init-package-json": "^8.2.5", + "is-cidr": "^6.0.3", + "json-parse-even-better-errors": "^5.0.0", + "libnpmaccess": "^10.0.3", + "libnpmdiff": "^8.1.5", + "libnpmexec": "^10.2.5", + "libnpmfund": "^7.0.19", + "libnpmorg": "^8.0.1", + "libnpmpack": "^9.1.5", + "libnpmpublish": "^11.1.3", + "libnpmsearch": "^9.0.1", + "libnpmteam": "^8.0.2", + "libnpmversion": "^8.0.3", + "make-fetch-happen": "^15.0.5", + "minimatch": "^10.2.4", + "minipass": "^7.1.3", "minipass-pipeline": "^1.2.4", - "mkdirp": "^1.0.4", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^9.0.0", - "tar": "^6.1.11", - "unique-filename": "^2.0.0" + "ms": "^2.1.2", + "node-gyp": "^12.2.0", + "nopt": "^9.0.0", + "npm-audit-report": "^7.0.0", + "npm-install-checks": "^8.0.0", + "npm-package-arg": "^13.0.2", + "npm-pick-manifest": "^11.0.3", + "npm-profile": "^12.0.1", + "npm-registry-fetch": "^19.1.1", + "npm-user-validate": "^4.0.0", + "p-map": "^7.0.4", + "pacote": "^21.5.0", + "parse-conflict-json": "^5.0.1", + "proc-log": "^6.1.0", + "qrcode-terminal": "^0.12.0", + "read": "^5.0.1", + "semver": "^7.7.4", + "spdx-expression-parse": "^4.0.0", + "ssri": "^13.0.1", + "supports-color": "^10.2.2", + "tar": "^7.5.11", + "text-table": "~0.2.0", + "tiny-relative-date": "^2.0.2", + "treeverse": "^3.0.0", + "validate-npm-package-name": "^7.0.2", + "which": "^6.0.1" + }, + "bin": { + "npm": "bin/npm-cli.js", + "npx": "bin/npx-cli.js" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/chalk": { - "version": "4.1.2", + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "path-key": "^3.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=8" } }, - "node_modules/npm/node_modules/chownr": { - "version": "2.0.0", + "node_modules/npm/node_modules/@gar/promise-retry": { + "version": "1.0.3", "dev": true, "inBundle": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": ">=10" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/cidr-regex": { - "version": "3.1.1", + "node_modules/npm/node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", "dev": true, "inBundle": true, - "license": "BSD-2-Clause", + "license": "ISC", "dependencies": { - "ip-regex": "^4.1.0" + "minipass": "^7.0.4" }, "engines": { - "node": ">=10" + "node": ">=18.0.0" } }, - "node_modules/npm/node_modules/clean-stack": { - "version": "2.2.0", + "node_modules/npm/node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", "dev": true, "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } + "license": "ISC" }, - "node_modules/npm/node_modules/cli-columns": { + "node_modules/npm/node_modules/@npmcli/agent": { "version": "4.0.0", "dev": true, "inBundle": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^11.2.1", + "socks-proxy-agent": "^8.0.3" }, "engines": { - "node": ">= 10" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/cli-table3": { - "version": "0.6.2", + "node_modules/npm/node_modules/@npmcli/arborist": { + "version": "9.4.2", "dev": true, "inBundle": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "string-width": "^4.2.0" + "@gar/promise-retry": "^1.0.0", + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^5.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/map-workspaces": "^5.0.0", + "@npmcli/metavuln-calculator": "^9.0.2", + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/query": "^5.0.0", + "@npmcli/redact": "^4.0.0", + "@npmcli/run-script": "^10.0.0", + "bin-links": "^6.0.0", + "cacache": "^20.0.1", + "common-ancestor-path": "^2.0.0", + "hosted-git-info": "^9.0.0", + "json-stringify-nice": "^1.1.4", + "lru-cache": "^11.2.1", + "minimatch": "^10.0.3", + "nopt": "^9.0.0", + "npm-install-checks": "^8.0.0", + "npm-package-arg": "^13.0.0", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "pacote": "^21.0.2", + "parse-conflict-json": "^5.0.1", + "proc-log": "^6.0.0", + "proggy": "^4.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "semver": "^7.3.7", + "ssri": "^13.0.0", + "treeverse": "^3.0.0", + "walk-up-path": "^4.0.0" }, - "engines": { - "node": "10.* || >= 12.*" + "bin": { + "arborist": "bin/index.js" }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "node_modules/npm/node_modules/clone": { - "version": "1.0.4", - "dev": true, - "inBundle": true, - "license": "MIT", "engines": { - "node": ">=0.8" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/cmd-shim": { - "version": "5.0.0", + "node_modules/npm/node_modules/@npmcli/config": { + "version": "10.8.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "mkdirp-infer-owner": "^2.0.0" + "@npmcli/map-workspaces": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "ci-info": "^4.0.0", + "ini": "^6.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "walk-up-path": "^4.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/color-convert": { - "version": "2.0.1", + "node_modules/npm/node_modules/@npmcli/fs": { + "version": "5.0.0", "dev": true, "inBundle": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "color-name": "~1.1.4" + "semver": "^7.3.5" }, "engines": { - "node": ">=7.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/color-support": { - "version": "1.1.3", + "node_modules/npm/node_modules/@npmcli/git": { + "version": "7.0.2", "dev": true, "inBundle": true, "license": "ISC", - "bin": { - "color-support": "bin.js" - } - }, - "node_modules/npm/node_modules/columnify": { - "version": "1.6.0", - "dev": true, - "inBundle": true, - "license": "MIT", "dependencies": { - "strip-ansi": "^6.0.1", - "wcwidth": "^1.0.0" + "@gar/promise-retry": "^1.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "ini": "^6.0.0", + "lru-cache": "^11.2.1", + "npm-pick-manifest": "^11.0.1", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "which": "^6.0.0" }, "engines": { - "node": ">=8.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/common-ancestor-path": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/concat-map": { - "version": "0.0.1", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/console-control-strings": { - "version": "1.1.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/cssesc": { - "version": "3.0.0", + "node_modules/npm/node_modules/@npmcli/installed-package-contents": { + "version": "4.0.0", "dev": true, "inBundle": true, - "license": "MIT", + "license": "ISC", + "dependencies": { + "npm-bundled": "^5.0.0", + "npm-normalize-package-bin": "^5.0.0" + }, "bin": { - "cssesc": "bin/cssesc" + "installed-package-contents": "bin/index.js" }, "engines": { - "node": ">=4" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/debug": { - "version": "4.3.4", + "node_modules/npm/node_modules/@npmcli/map-workspaces": { + "version": "5.0.3", "dev": true, "inBundle": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "glob": "^13.0.0", + "minimatch": "^10.0.3" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/npm/node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/debuglog": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", "engines": { - "node": "*" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/defaults": { - "version": "1.0.3", + "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { + "version": "9.0.3", "dev": true, "inBundle": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "clone": "^1.0.2" - } - }, - "node_modules/npm/node_modules/delegates": { - "version": "1.0.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/depd": { - "version": "1.1.2", - "dev": true, - "inBundle": true, - "license": "MIT", + "cacache": "^20.0.0", + "json-parse-even-better-errors": "^5.0.0", + "pacote": "^21.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5" + }, "engines": { - "node": ">= 0.6" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/dezalgo": { - "version": "1.0.4", + "node_modules/npm/node_modules/@npmcli/name-from-folder": { + "version": "4.0.0", "dev": true, "inBundle": true, "license": "ISC", - "dependencies": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, - "node_modules/npm/node_modules/diff": { - "version": "5.1.0", - "dev": true, - "inBundle": true, - "license": "BSD-3-Clause", "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/npm/node_modules/emoji-regex": { - "version": "8.0.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/encoding": { - "version": "0.1.13", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/env-paths": { - "version": "2.2.1", + "node_modules/npm/node_modules/@npmcli/node-gyp": { + "version": "5.0.0", "dev": true, "inBundle": true, - "license": "MIT", + "license": "ISC", "engines": { - "node": ">=6" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/err-code": { - "version": "2.0.3", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/fastest-levenshtein": { - "version": "1.0.12", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/fs-minipass": { - "version": "2.1.0", + "node_modules/npm/node_modules/@npmcli/package-json": { + "version": "7.0.5", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "minipass": "^3.0.0" + "@npmcli/git": "^7.0.0", + "glob": "^13.0.0", + "hosted-git-info": "^9.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", + "semver": "^7.5.3", + "spdx-expression-parse": "^4.0.0" }, "engines": { - "node": ">= 8" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/fs.realpath": { - "version": "1.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/function-bind": { - "version": "1.1.1", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/gauge": { - "version": "4.0.4", + "node_modules/npm/node_modules/@npmcli/promise-spawn": { + "version": "9.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" + "which": "^6.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/glob": { - "version": "8.0.3", + "node_modules/npm/node_modules/@npmcli/query": { + "version": "5.0.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "postcss-selector-parser": "^7.0.0" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/graceful-fs": { - "version": "4.2.10", + "node_modules/npm/node_modules/@npmcli/redact": { + "version": "4.0.0", "dev": true, "inBundle": true, - "license": "ISC" + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } }, - "node_modules/npm/node_modules/has": { - "version": "1.0.3", + "node_modules/npm/node_modules/@npmcli/run-script": { + "version": "10.0.4", "dev": true, "inBundle": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "function-bind": "^1.1.1" + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "node-gyp": "^12.1.0", + "proc-log": "^6.0.0" }, "engines": { - "node": ">= 0.4.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/has-flag": { + "node_modules/npm/node_modules/@sigstore/bundle": { "version": "4.0.0", "dev": true, "inBundle": true, - "license": "MIT", + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.5.0" + }, "engines": { - "node": ">=8" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/has-unicode": { - "version": "2.0.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/hosted-git-info": { - "version": "5.2.1", + "node_modules/npm/node_modules/@sigstore/core": { + "version": "3.2.0", "dev": true, "inBundle": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^7.5.1" - }, + "license": "Apache-2.0", "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/http-cache-semantics": { - "version": "4.1.0", + "node_modules/npm/node_modules/@sigstore/protobuf-specs": { + "version": "0.5.0", "dev": true, "inBundle": true, - "license": "BSD-2-Clause" + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/npm/node_modules/http-proxy-agent": { - "version": "5.0.0", + "node_modules/npm/node_modules/@sigstore/sign": { + "version": "4.1.1", "dev": true, "inBundle": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" + "@gar/promise-retry": "^1.0.2", + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.2.0", + "@sigstore/protobuf-specs": "^0.5.0", + "make-fetch-happen": "^15.0.4", + "proc-log": "^6.1.0" }, "engines": { - "node": ">= 6" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/https-proxy-agent": { - "version": "5.0.1", + "node_modules/npm/node_modules/@sigstore/tuf": { + "version": "4.0.2", "dev": true, "inBundle": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "agent-base": "6", - "debug": "4" + "@sigstore/protobuf-specs": "^0.5.0", + "tuf-js": "^4.1.0" }, "engines": { - "node": ">= 6" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/humanize-ms": { - "version": "1.2.1", + "node_modules/npm/node_modules/@sigstore/verify": { + "version": "3.1.0", "dev": true, "inBundle": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "ms": "^2.0.0" + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/iconv-lite": { - "version": "0.6.3", + "node_modules/npm/node_modules/@tufjs/canonical-json": { + "version": "2.0.0", "dev": true, "inBundle": true, "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, "engines": { - "node": ">=0.10.0" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/ignore-walk": { - "version": "5.0.1", + "node_modules/npm/node_modules/@tufjs/models": { + "version": "4.1.0", "dev": true, "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "minimatch": "^5.0.1" + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^10.1.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/imurmurhash": { - "version": "0.1.4", + "node_modules/npm/node_modules/abbrev": { + "version": "4.0.0", "dev": true, "inBundle": true, - "license": "MIT", + "license": "ISC", "engines": { - "node": ">=0.8.19" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/indent-string": { - "version": "4.0.0", + "node_modules/npm/node_modules/agent-base": { + "version": "7.1.4", "dev": true, "inBundle": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 14" } }, - "node_modules/npm/node_modules/infer-owner": { - "version": "1.0.4", + "node_modules/npm/node_modules/aproba": { + "version": "2.1.0", "dev": true, "inBundle": true, "license": "ISC" }, - "node_modules/npm/node_modules/inflight": { - "version": "1.0.6", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/npm/node_modules/inherits": { - "version": "2.0.4", + "node_modules/npm/node_modules/archy": { + "version": "1.0.0", "dev": true, "inBundle": true, - "license": "ISC" + "license": "MIT" }, - "node_modules/npm/node_modules/ini": { - "version": "3.0.1", + "node_modules/npm/node_modules/balanced-match": { + "version": "4.0.4", "dev": true, "inBundle": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "18 || 20 || >=22" } }, - "node_modules/npm/node_modules/init-package-json": { - "version": "3.0.2", + "node_modules/npm/node_modules/bin-links": { + "version": "6.0.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "npm-package-arg": "^9.0.1", - "promzard": "^0.3.0", - "read": "^1.0.7", - "read-package-json": "^5.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4", - "validate-npm-package-name": "^4.0.0" + "cmd-shim": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "proc-log": "^6.0.0", + "read-cmd-shim": "^6.0.0", + "write-file-atomic": "^7.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/ip": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/ip-regex": { - "version": "4.3.0", + "node_modules/npm/node_modules/binary-extensions": { + "version": "3.1.0", "dev": true, "inBundle": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/is-cidr": { - "version": "4.0.2", + "node_modules/npm/node_modules/brace-expansion": { + "version": "5.0.4", "dev": true, "inBundle": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "cidr-regex": "^3.1.1" + "balanced-match": "^4.0.2" }, "engines": { - "node": ">=10" + "node": "18 || 20 || >=22" } }, - "node_modules/npm/node_modules/is-core-module": { - "version": "2.10.0", + "node_modules/npm/node_modules/cacache": { + "version": "20.0.4", "dev": true, "inBundle": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "has": "^1.0.3" + "@npmcli/fs": "^5.0.0", + "fs-minipass": "^3.0.0", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^13.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", + "node_modules/npm/node_modules/chalk": { + "version": "5.6.2", "dev": true, "inBundle": true, "license": "MIT", "engines": { - "node": ">=8" + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/npm/node_modules/is-lambda": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/isexe": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/json-stringify-nice": { - "version": "1.1.4", + "node_modules/npm/node_modules/chownr": { + "version": "3.0.0", "dev": true, "inBundle": true, - "license": "ISC", - "funding": { - "url": "https://github.com/sponsors/isaacs" + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" } }, - "node_modules/npm/node_modules/jsonparse": { - "version": "1.3.1", + "node_modules/npm/node_modules/ci-info": { + "version": "4.4.0", "dev": true, - "engines": [ - "node >= 0.2.0" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } ], "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/just-diff": { - "version": "5.1.1", - "dev": true, - "inBundle": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/npm/node_modules/just-diff-apply": { - "version": "5.4.1", + "node_modules/npm/node_modules/cidr-regex": { + "version": "5.0.3", "dev": true, "inBundle": true, - "license": "MIT" + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } }, - "node_modules/npm/node_modules/libnpmaccess": { - "version": "6.0.4", + "node_modules/npm/node_modules/cmd-shim": { + "version": "8.0.0", "dev": true, "inBundle": true, "license": "ISC", - "dependencies": { - "aproba": "^2.0.0", - "minipass": "^3.1.1", - "npm-package-arg": "^9.0.1", - "npm-registry-fetch": "^13.0.0" - }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/libnpmdiff": { - "version": "4.0.5", + "node_modules/npm/node_modules/common-ancestor-path": { + "version": "2.0.0", "dev": true, "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/disparity-colors": "^2.0.0", - "@npmcli/installed-package-contents": "^1.0.7", - "binary-extensions": "^2.2.0", - "diff": "^5.1.0", - "minimatch": "^5.0.1", - "npm-package-arg": "^9.0.1", - "pacote": "^13.6.1", - "tar": "^6.1.0" - }, + "license": "BlueOak-1.0.0", "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">= 18" } }, - "node_modules/npm/node_modules/libnpmexec": { - "version": "4.0.14", + "node_modules/npm/node_modules/cssesc": { + "version": "3.0.0", "dev": true, "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/arborist": "^5.6.3", - "@npmcli/ci-detect": "^2.0.0", - "@npmcli/fs": "^2.1.1", - "@npmcli/run-script": "^4.2.0", - "chalk": "^4.1.0", - "mkdirp-infer-owner": "^2.0.0", - "npm-package-arg": "^9.0.1", - "npmlog": "^6.0.2", - "pacote": "^13.6.1", - "proc-log": "^2.0.0", - "read": "^1.0.7", - "read-package-json-fast": "^2.0.2", - "semver": "^7.3.7", - "walk-up-path": "^1.0.0" + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=4" } }, - "node_modules/npm/node_modules/libnpmfund": { - "version": "3.0.5", + "node_modules/npm/node_modules/debug": { + "version": "4.4.3", "dev": true, "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "@npmcli/arborist": "^5.6.3" + "ms": "^2.1.3" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/npm/node_modules/libnpmhook": { - "version": "8.0.4", + "node_modules/npm/node_modules/diff": { + "version": "8.0.3", "dev": true, "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^13.0.0" - }, + "license": "BSD-3-Clause", "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=0.3.1" } }, - "node_modules/npm/node_modules/libnpmorg": { - "version": "4.0.4", + "node_modules/npm/node_modules/env-paths": { + "version": "2.2.1", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^13.0.0" - }, + "inBundle": true, + "license": "MIT", "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=6" } }, - "node_modules/npm/node_modules/libnpmpack": { - "version": "4.1.3", + "node_modules/npm/node_modules/exponential-backoff": { + "version": "3.1.3", "dev": true, "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/run-script": "^4.1.3", - "npm-package-arg": "^9.0.1", - "pacote": "^13.6.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } + "license": "Apache-2.0" }, - "node_modules/npm/node_modules/libnpmpublish": { - "version": "6.0.5", + "node_modules/npm/node_modules/fastest-levenshtein": { + "version": "1.0.16", "dev": true, "inBundle": true, - "license": "ISC", - "dependencies": { - "normalize-package-data": "^4.0.0", - "npm-package-arg": "^9.0.1", - "npm-registry-fetch": "^13.0.0", - "semver": "^7.3.7", - "ssri": "^9.0.0" - }, + "license": "MIT", "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">= 4.9.1" } }, - "node_modules/npm/node_modules/libnpmsearch": { - "version": "5.0.4", + "node_modules/npm/node_modules/fs-minipass": { + "version": "3.0.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "npm-registry-fetch": "^13.0.0" + "minipass": "^7.0.3" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/libnpmteam": { - "version": "4.0.4", + "node_modules/npm/node_modules/glob": { + "version": "13.0.6", "dev": true, "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^13.0.0" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/npm/node_modules/libnpmversion": { - "version": "3.0.7", + "node_modules/npm/node_modules/graceful-fs": { + "version": "4.2.11", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/hosted-git-info": { + "version": "9.0.2", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/git": "^3.0.0", - "@npmcli/run-script": "^4.1.3", - "json-parse-even-better-errors": "^2.3.1", - "proc-log": "^2.0.0", - "semver": "^7.3.7" + "lru-cache": "^11.1.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/lru-cache": { - "version": "7.13.2", + "node_modules/npm/node_modules/http-cache-semantics": { + "version": "4.2.0", "dev": true, "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=12" - } + "license": "BSD-2-Clause" }, - "node_modules/npm/node_modules/make-fetch-happen": { - "version": "10.2.1", + "node_modules/npm/node_modules/http-proxy-agent": { + "version": "7.0.2", "dev": true, "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^16.1.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^2.0.3", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^9.0.0" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">= 14" } }, - "node_modules/npm/node_modules/minimatch": { - "version": "5.1.0", + "node_modules/npm/node_modules/https-proxy-agent": { + "version": "7.0.6", "dev": true, "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" + "agent-base": "^7.1.2", + "debug": "4" }, "engines": { - "node": ">=10" + "node": ">= 14" } }, - "node_modules/npm/node_modules/minipass": { - "version": "3.3.4", + "node_modules/npm/node_modules/iconv-lite": { + "version": "0.7.2", "dev": true, "inBundle": true, - "license": "ISC", + "license": "MIT", + "optional": true, "dependencies": { - "yallist": "^4.0.0" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": ">=8" + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/npm/node_modules/minipass-collect": { - "version": "1.0.2", + "node_modules/npm/node_modules/ignore-walk": { + "version": "8.0.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "minipass": "^3.0.0" + "minimatch": "^10.0.3" }, "engines": { - "node": ">= 8" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/minipass-fetch": { - "version": "2.1.1", + "node_modules/npm/node_modules/ini": { + "version": "6.0.0", "dev": true, "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^3.1.6", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - }, + "license": "ISC", "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/minipass-flush": { - "version": "1.0.5", + "node_modules/npm/node_modules/init-package-json": { + "version": "8.2.5", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "minipass": "^3.0.0" + "@npmcli/package-json": "^7.0.0", + "npm-package-arg": "^13.0.0", + "promzard": "^3.0.1", + "read": "^5.0.1", + "semver": "^7.7.2", + "validate-npm-package-name": "^7.0.0" }, "engines": { - "node": ">= 8" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/minipass-json-stream": { - "version": "1.0.1", + "node_modules/npm/node_modules/ip-address": { + "version": "10.1.0", "dev": true, "inBundle": true, "license": "MIT", - "dependencies": { - "jsonparse": "^1.3.1", - "minipass": "^3.0.0" + "engines": { + "node": ">= 12" } }, - "node_modules/npm/node_modules/minipass-pipeline": { - "version": "1.2.4", + "node_modules/npm/node_modules/is-cidr": { + "version": "6.0.3", "dev": true, "inBundle": true, - "license": "ISC", + "license": "BSD-2-Clause", "dependencies": { - "minipass": "^3.0.0" + "cidr-regex": "^5.0.1" }, "engines": { - "node": ">=8" + "node": ">=20" } }, - "node_modules/npm/node_modules/minipass-sized": { - "version": "1.0.3", + "node_modules/npm/node_modules/isexe": { + "version": "4.0.0", "dev": true, "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, + "license": "BlueOak-1.0.0", "engines": { - "node": ">=8" + "node": ">=20" } }, - "node_modules/npm/node_modules/minizlib": { - "version": "2.1.2", + "node_modules/npm/node_modules/json-parse-even-better-errors": { + "version": "5.0.0", "dev": true, "inBundle": true, "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, "engines": { - "node": ">= 8" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/mkdirp": { - "version": "1.0.4", + "node_modules/npm/node_modules/json-stringify-nice": { + "version": "1.1.4", "dev": true, "inBundle": true, - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/npm/node_modules/mkdirp-infer-owner": { - "version": "2.0.0", + "node_modules/npm/node_modules/jsonparse": { + "version": "1.3.1", "dev": true, + "engines": [ + "node >= 0.2.0" + ], "inBundle": true, - "license": "ISC", - "dependencies": { - "chownr": "^2.0.0", - "infer-owner": "^1.0.4", - "mkdirp": "^1.0.3" - }, - "engines": { - "node": ">=10" - } + "license": "MIT" }, - "node_modules/npm/node_modules/ms": { - "version": "2.1.3", + "node_modules/npm/node_modules/just-diff": { + "version": "6.0.2", "dev": true, "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/mute-stream": { - "version": "0.0.8", + "node_modules/npm/node_modules/just-diff-apply": { + "version": "5.5.0", "dev": true, "inBundle": true, - "license": "ISC" + "license": "MIT" }, - "node_modules/npm/node_modules/negotiator": { - "version": "0.6.3", + "node_modules/npm/node_modules/libnpmaccess": { + "version": "10.0.3", "dev": true, "inBundle": true, - "license": "MIT", + "license": "ISC", + "dependencies": { + "npm-package-arg": "^13.0.0", + "npm-registry-fetch": "^19.0.0" + }, "engines": { - "node": ">= 0.6" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/node-gyp": { - "version": "9.1.0", + "node_modules/npm/node_modules/libnpmdiff": { + "version": "8.1.5", "dev": true, "inBundle": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^10.0.3", - "nopt": "^5.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" + "@npmcli/arborist": "^9.4.2", + "@npmcli/installed-package-contents": "^4.0.0", + "binary-extensions": "^3.0.0", + "diff": "^8.0.2", + "minimatch": "^10.0.3", + "npm-package-arg": "^13.0.0", + "pacote": "^21.0.2", + "tar": "^7.5.1" }, "engines": { - "node": "^12.22 || ^14.13 || >=16" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/node-gyp/node_modules/brace-expansion": { - "version": "1.1.11", + "node_modules/npm/node_modules/libnpmexec": { + "version": "10.2.5", "dev": true, "inBundle": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@gar/promise-retry": "^1.0.0", + "@npmcli/arborist": "^9.4.2", + "@npmcli/package-json": "^7.0.0", + "@npmcli/run-script": "^10.0.0", + "ci-info": "^4.0.0", + "npm-package-arg": "^13.0.0", + "pacote": "^21.0.2", + "proc-log": "^6.0.0", + "read": "^5.0.1", + "semver": "^7.3.7", + "signal-exit": "^4.1.0", + "walk-up-path": "^4.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/node-gyp/node_modules/glob": { - "version": "7.2.3", + "node_modules/npm/node_modules/libnpmfund": { + "version": "7.0.19", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "@npmcli/arborist": "^9.4.2" }, "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/node-gyp/node_modules/minimatch": { - "version": "3.1.2", + "node_modules/npm/node_modules/libnpmorg": { + "version": "8.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "aproba": "^2.0.0", + "npm-registry-fetch": "^19.0.0" }, "engines": { - "node": "*" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/node-gyp/node_modules/nopt": { - "version": "5.0.0", + "node_modules/npm/node_modules/libnpmpack": { + "version": "9.1.5", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" + "@npmcli/arborist": "^9.4.2", + "@npmcli/run-script": "^10.0.0", + "npm-package-arg": "^13.0.0", + "pacote": "^21.0.2" }, "engines": { - "node": ">=6" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/nopt": { - "version": "6.0.0", + "node_modules/npm/node_modules/libnpmpublish": { + "version": "11.1.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "abbrev": "^1.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" + "@npmcli/package-json": "^7.0.0", + "ci-info": "^4.0.0", + "npm-package-arg": "^13.0.0", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.7", + "sigstore": "^4.0.0", + "ssri": "^13.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/normalize-package-data": { - "version": "4.0.1", + "node_modules/npm/node_modules/libnpmsearch": { + "version": "9.0.1", "dev": true, "inBundle": true, - "license": "BSD-2-Clause", + "license": "ISC", "dependencies": { - "hosted-git-info": "^5.0.0", - "is-core-module": "^2.8.1", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" + "npm-registry-fetch": "^19.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/npm-audit-report": { - "version": "3.0.0", + "node_modules/npm/node_modules/libnpmteam": { + "version": "8.0.2", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "chalk": "^4.0.0" + "aproba": "^2.0.0", + "npm-registry-fetch": "^19.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/npm-bundled": { - "version": "2.0.1", + "node_modules/npm/node_modules/libnpmversion": { + "version": "8.0.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "npm-normalize-package-bin": "^2.0.0" + "@npmcli/git": "^7.0.0", + "@npmcli/run-script": "^10.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.7" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/npm-bundled/node_modules/npm-normalize-package-bin": { - "version": "2.0.0", + "node_modules/npm/node_modules/lru-cache": { + "version": "11.2.7", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/npm/node_modules/make-fetch-happen": { + "version": "15.0.5", "dev": true, "inBundle": true, "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/agent": "^4.0.0", + "@npmcli/redact": "^4.0.0", + "cacache": "^20.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^6.0.0", + "ssri": "^13.0.0" + }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/npm-install-checks": { - "version": "5.0.0", + "node_modules/npm/node_modules/minimatch": { + "version": "10.2.4", "dev": true, "inBundle": true, - "license": "BSD-2-Clause", + "license": "BlueOak-1.0.0", "dependencies": { - "semver": "^7.1.1" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/npm/node_modules/npm-normalize-package-bin": { - "version": "1.0.1", + "node_modules/npm/node_modules/minipass": { + "version": "7.1.3", "dev": true, "inBundle": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } }, - "node_modules/npm/node_modules/npm-package-arg": { - "version": "9.1.0", + "node_modules/npm/node_modules/minipass-collect": { + "version": "2.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "hosted-git-info": "^5.0.0", - "proc-log": "^2.0.1", - "semver": "^7.3.5", - "validate-npm-package-name": "^4.0.0" + "minipass": "^7.0.3" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/npm/node_modules/npm-packlist": { - "version": "5.1.3", + "node_modules/npm/node_modules/minipass-fetch": { + "version": "5.0.2", "dev": true, "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "glob": "^8.0.1", - "ignore-walk": "^5.0.1", - "npm-bundled": "^2.0.0", - "npm-normalize-package-bin": "^2.0.0" - }, - "bin": { - "npm-packlist": "bin/index.js" + "minipass": "^7.0.3", + "minipass-sized": "^2.0.0", + "minizlib": "^3.0.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^20.17.0 || >=22.9.0" + }, + "optionalDependencies": { + "iconv-lite": "^0.7.2" } }, - "node_modules/npm/node_modules/npm-packlist/node_modules/npm-normalize-package-bin": { - "version": "2.0.0", + "node_modules/npm/node_modules/minipass-flush": { + "version": "1.0.5", "dev": true, "inBundle": true, "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">= 8" } }, - "node_modules/npm/node_modules/npm-pick-manifest": { - "version": "7.0.2", + "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "npm-install-checks": "^5.0.0", - "npm-normalize-package-bin": "^2.0.0", - "npm-package-arg": "^9.0.0", - "semver": "^7.3.5" + "yallist": "^4.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=8" } }, - "node_modules/npm/node_modules/npm-pick-manifest/node_modules/npm-normalize-package-bin": { - "version": "2.0.0", + "node_modules/npm/node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", "dev": true, "inBundle": true, - "license": "ISC", - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } + "license": "ISC" }, - "node_modules/npm/node_modules/npm-profile": { - "version": "6.2.1", + "node_modules/npm/node_modules/minipass-pipeline": { + "version": "1.2.4", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "npm-registry-fetch": "^13.0.1", - "proc-log": "^2.0.0" + "minipass": "^3.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=8" } }, - "node_modules/npm/node_modules/npm-registry-fetch": { - "version": "13.3.1", + "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "make-fetch-happen": "^10.0.6", - "minipass": "^3.1.6", - "minipass-fetch": "^2.0.3", - "minipass-json-stream": "^1.0.1", - "minizlib": "^2.1.2", - "npm-package-arg": "^9.0.1", - "proc-log": "^2.0.0" + "yallist": "^4.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=8" } }, - "node_modules/npm/node_modules/npm-user-validate": { - "version": "1.0.1", + "node_modules/npm/node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", "dev": true, "inBundle": true, - "license": "BSD-2-Clause" + "license": "ISC" }, - "node_modules/npm/node_modules/npmlog": { - "version": "6.0.2", + "node_modules/npm/node_modules/minipass-sized": { + "version": "2.0.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" + "minipass": "^7.1.2" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=8" } }, - "node_modules/npm/node_modules/once": { - "version": "1.4.0", + "node_modules/npm/node_modules/minizlib": { + "version": "3.1.0", "dev": true, "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "wrappy": "1" + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" } }, - "node_modules/npm/node_modules/opener": { - "version": "1.5.2", + "node_modules/npm/node_modules/ms": { + "version": "2.1.3", "dev": true, "inBundle": true, - "license": "(WTFPL OR MIT)", - "bin": { - "opener": "bin/opener-bin.js" + "license": "MIT" + }, + "node_modules/npm/node_modules/mute-stream": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/p-map": { - "version": "4.0.0", + "node_modules/npm/node_modules/negotiator": { + "version": "1.0.0", "dev": true, "inBundle": true, "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.6" } }, - "node_modules/npm/node_modules/pacote": { - "version": "13.6.2", + "node_modules/npm/node_modules/node-gyp": { + "version": "12.2.0", "dev": true, "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "@npmcli/git": "^3.0.0", - "@npmcli/installed-package-contents": "^1.0.7", - "@npmcli/promise-spawn": "^3.0.0", - "@npmcli/run-script": "^4.1.0", - "cacache": "^16.0.0", - "chownr": "^2.0.0", - "fs-minipass": "^2.1.0", - "infer-owner": "^1.0.4", - "minipass": "^3.1.6", - "mkdirp": "^1.0.4", - "npm-package-arg": "^9.0.0", - "npm-packlist": "^5.1.0", - "npm-pick-manifest": "^7.0.0", - "npm-registry-fetch": "^13.0.1", - "proc-log": "^2.0.0", - "promise-retry": "^2.0.1", - "read-package-json": "^5.0.0", - "read-package-json-fast": "^2.0.3", - "rimraf": "^3.0.2", - "ssri": "^9.0.0", - "tar": "^6.1.11" + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^15.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "which": "^6.0.0" }, "bin": { - "pacote": "lib/bin.js" + "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/parse-conflict-json": { - "version": "2.0.2", + "node_modules/npm/node_modules/nopt": { + "version": "9.0.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "json-parse-even-better-errors": "^2.3.1", - "just-diff": "^5.0.1", - "just-diff-apply": "^5.2.0" + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/path-is-absolute": { - "version": "1.0.1", + "node_modules/npm/node_modules/npm-audit-report": { + "version": "7.0.0", "dev": true, "inBundle": true, - "license": "MIT", + "license": "ISC", "engines": { - "node": ">=0.10.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/postcss-selector-parser": { - "version": "6.0.10", + "node_modules/npm/node_modules/npm-bundled": { + "version": "5.0.0", "dev": true, "inBundle": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" + "npm-normalize-package-bin": "^5.0.0" }, "engines": { - "node": ">=4" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/proc-log": { - "version": "2.0.1", + "node_modules/npm/node_modules/npm-install-checks": { + "version": "8.0.0", "dev": true, "inBundle": true, - "license": "ISC", + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/promise-all-reject-late": { - "version": "1.0.1", + "node_modules/npm/node_modules/npm-normalize-package-bin": { + "version": "5.0.0", "dev": true, "inBundle": true, "license": "ISC", - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/promise-call-limit": { - "version": "1.0.1", + "node_modules/npm/node_modules/npm-package-arg": { + "version": "13.0.2", "dev": true, "inBundle": true, "license": "ISC", - "funding": { - "url": "https://github.com/sponsors/isaacs" + "dependencies": { + "hosted-git-info": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/promise-inflight": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/promise-retry": { - "version": "2.0.1", + "node_modules/npm/node_modules/npm-packlist": { + "version": "10.0.4", "dev": true, "inBundle": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" + "ignore-walk": "^8.0.0", + "proc-log": "^6.0.0" }, "engines": { - "node": ">=10" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/promzard": { - "version": "0.3.0", + "node_modules/npm/node_modules/npm-pick-manifest": { + "version": "11.0.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "read": "1" + "npm-install-checks": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "npm-package-arg": "^13.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/qrcode-terminal": { - "version": "0.12.0", + "node_modules/npm/node_modules/npm-profile": { + "version": "12.0.1", "dev": true, "inBundle": true, - "bin": { - "qrcode-terminal": "bin/qrcode-terminal.js" + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/read": { - "version": "1.0.7", + "node_modules/npm/node_modules/npm-registry-fetch": { + "version": "19.1.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "mute-stream": "~0.0.4" + "@npmcli/redact": "^4.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^15.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^13.0.0", + "proc-log": "^6.0.0" }, "engines": { - "node": ">=0.8" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/read-cmd-shim": { - "version": "3.0.0", + "node_modules/npm/node_modules/npm-user-validate": { + "version": "4.0.0", "dev": true, "inBundle": true, - "license": "ISC", + "license": "BSD-2-Clause", "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/read-package-json": { - "version": "5.0.2", + "node_modules/npm/node_modules/p-map": { + "version": "7.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/pacote": { + "version": "21.5.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "glob": "^8.0.1", - "json-parse-even-better-errors": "^2.3.1", - "normalize-package-data": "^4.0.0", - "npm-normalize-package-bin": "^2.0.0" + "@gar/promise-retry": "^1.0.0", + "@npmcli/git": "^7.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "@npmcli/run-script": "^10.0.0", + "cacache": "^20.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^13.0.0", + "npm-packlist": "^10.0.1", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0", + "sigstore": "^4.0.0", + "ssri": "^13.0.0", + "tar": "^7.4.3" + }, + "bin": { + "pacote": "bin/index.js" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/read-package-json-fast": { - "version": "2.0.3", + "node_modules/npm/node_modules/parse-conflict-json": { + "version": "5.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "json-parse-even-better-errors": "^2.3.0", - "npm-normalize-package-bin": "^1.0.1" + "json-parse-even-better-errors": "^5.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" }, "engines": { - "node": ">=10" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/read-package-json/node_modules/npm-normalize-package-bin": { - "version": "2.0.0", + "node_modules/npm/node_modules/path-scurry": { + "version": "2.0.2", "dev": true, "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/npm/node_modules/readable-stream": { - "version": "3.6.0", + "node_modules/npm/node_modules/postcss-selector-parser": { + "version": "7.1.1", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, "engines": { - "node": ">= 6" + "node": ">=4" } }, - "node_modules/npm/node_modules/readdir-scoped-modules": { - "version": "1.1.0", + "node_modules/npm/node_modules/proc-log": { + "version": "6.1.0", "dev": true, "inBundle": true, "license": "ISC", - "dependencies": { - "debuglog": "^1.0.1", - "dezalgo": "^1.0.0", - "graceful-fs": "^4.1.2", - "once": "^1.3.0" + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/retry": { - "version": "0.12.0", + "node_modules/npm/node_modules/proggy": { + "version": "4.0.0", "dev": true, "inBundle": true, - "license": "MIT", + "license": "ISC", "engines": { - "node": ">= 4" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/rimraf": { - "version": "3.0.2", + "node_modules/npm/node_modules/promise-all-reject-late": { + "version": "1.0.1", "dev": true, "inBundle": true, "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/npm/node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.11", + "node_modules/npm/node_modules/promise-call-limit": { + "version": "3.0.2", "dev": true, "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/npm/node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", + "node_modules/npm/node_modules/promzard": { + "version": "3.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "read": "^5.0.0" }, "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.2", + "node_modules/npm/node_modules/qrcode-terminal": { + "version": "0.12.0", + "dev": true, + "inBundle": true, + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } + }, + "node_modules/npm/node_modules/read": { + "version": "5.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "mute-stream": "^3.0.0" }, "engines": { - "node": "*" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/safe-buffer": { - "version": "5.2.1", + "node_modules/npm/node_modules/read-cmd-shim": { + "version": "6.0.0", "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" - } - ], "inBundle": true, - "license": "MIT" + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } }, "node_modules/npm/node_modules/safer-buffer": { "version": "2.1.2", @@ -9143,13 +10696,10 @@ "optional": true }, "node_modules/npm/node_modules/semver": { - "version": "7.3.7", + "version": "7.7.4", "dev": true, "inBundle": true, "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -9157,29 +10707,34 @@ "node": ">=10" } }, - "node_modules/npm/node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", + "node_modules/npm/node_modules/signal-exit": { + "version": "4.1.0", "dev": true, "inBundle": true, "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, "engines": { - "node": ">=10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/npm/node_modules/set-blocking": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/signal-exit": { - "version": "3.0.7", + "node_modules/npm/node_modules/sigstore": { + "version": "4.1.0", "dev": true, "inBundle": true, - "license": "ISC" + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0", + "@sigstore/sign": "^4.1.0", + "@sigstore/tuf": "^4.0.1", + "@sigstore/verify": "^3.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } }, "node_modules/npm/node_modules/smart-buffer": { "version": "4.2.0", @@ -9192,51 +10747,41 @@ } }, "node_modules/npm/node_modules/socks": { - "version": "2.7.0", + "version": "2.8.7", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "ip": "^2.0.0", + "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" }, "engines": { - "node": ">= 10.13.0", + "node": ">= 10.0.0", "npm": ">= 3.0.0" } }, "node_modules/npm/node_modules/socks-proxy-agent": { - "version": "7.0.0", + "version": "8.0.5", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" }, "engines": { - "node": ">= 10" - } - }, - "node_modules/npm/node_modules/spdx-correct": { - "version": "3.1.1", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" + "node": ">= 14" } }, "node_modules/npm/node_modules/spdx-exceptions": { - "version": "2.3.0", + "version": "2.5.0", "dev": true, "inBundle": true, "license": "CC-BY-3.0" }, "node_modules/npm/node_modules/spdx-expression-parse": { - "version": "3.0.1", + "version": "4.0.0", "dev": true, "inBundle": true, "license": "MIT", @@ -9246,85 +10791,49 @@ } }, "node_modules/npm/node_modules/spdx-license-ids": { - "version": "3.0.11", + "version": "3.0.23", "dev": true, "inBundle": true, "license": "CC0-1.0" }, "node_modules/npm/node_modules/ssri": { - "version": "9.0.1", + "version": "13.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "minipass": "^3.1.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/string_decoder": { - "version": "1.3.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/npm/node_modules/string-width": { - "version": "4.2.3", - "dev": true, - "inBundle": 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/npm/node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" + "minipass": "^7.0.3" }, "engines": { - "node": ">=8" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/supports-color": { - "version": "7.2.0", + "version": "10.2.2", "dev": true, "inBundle": true, "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/npm/node_modules/tar": { - "version": "6.1.11", + "version": "7.5.11", "dev": true, "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" }, "engines": { - "node": ">= 10" + "node": ">=18" } }, "node_modules/npm/node_modules/text-table": { @@ -9334,42 +10843,78 @@ "license": "MIT" }, "node_modules/npm/node_modules/tiny-relative-date": { - "version": "1.3.0", + "version": "2.0.2", "dev": true, "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/treeverse": { - "version": "2.0.0", + "node_modules/npm/node_modules/tinyglobby": { + "version": "0.2.15", "dev": true, "inBundle": true, - "license": "ISC", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/npm/node_modules/unique-filename": { - "version": "2.0.1", + "node_modules/npm/node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", "dev": true, "inBundle": true, - "license": "ISC", - "dependencies": { - "unique-slug": "^3.0.0" + "license": "MIT", + "engines": { + "node": ">=12.0.0" }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/npm/node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "peer": true, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/npm/node_modules/unique-slug": { + "node_modules/npm/node_modules/treeverse": { "version": "3.0.0", "dev": true, "inBundle": true, "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/tuf-js": { + "version": "4.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", "dependencies": { - "imurmurhash": "^0.1.4" + "@tufjs/models": "4.1.0", + "debug": "^4.4.3", + "make-fetch-happen": "^15.0.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/util-deprecate": { @@ -9378,91 +10923,59 @@ "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/validate-npm-package-license": { - "version": "3.0.4", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, "node_modules/npm/node_modules/validate-npm-package-name": { - "version": "4.0.0", + "version": "7.0.2", "dev": true, "inBundle": true, "license": "ISC", - "dependencies": { - "builtins": "^5.0.0" - }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/walk-up-path": { - "version": "1.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/wcwidth": { - "version": "1.0.1", + "version": "4.0.0", "dev": true, "inBundle": true, - "license": "MIT", - "dependencies": { - "defaults": "^1.0.3" + "license": "ISC", + "engines": { + "node": "20 || >=22" } }, "node_modules/npm/node_modules/which": { - "version": "2.0.2", + "version": "6.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "isexe": "^2.0.0" + "isexe": "^4.0.0" }, "bin": { - "node-which": "bin/node-which" + "node-which": "bin/which.js" }, "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/wide-align": { - "version": "1.1.5", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/wrappy": { - "version": "1.0.2", - "dev": true, - "inBundle": true, - "license": "ISC" - }, "node_modules/npm/node_modules/write-file-atomic": { - "version": "4.0.2", + "version": "7.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" + "signal-exit": "^4.0.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/yallist": { - "version": "4.0.0", + "version": "5.0.0", "dev": true, "inBundle": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } }, "node_modules/nth-check": { "version": "2.1.1", @@ -9477,12 +10990,25 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", - "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", + "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" } @@ -9513,30 +11039,68 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-each-series": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", - "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-each-series": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-3.0.0.tgz", + "integrity": "sha512-lastgtAdoH9YaLyDa5i5z64q+kzOcQHsQ5SsZJD3q0VEyI8mq872S3geuNbRUQLVAE9siMfgKrpj7MloKFHruw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-event": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-6.0.1.tgz", + "integrity": "sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==", "dev": true, "license": "MIT", + "dependencies": { + "p-timeout": "^6.1.2" + }, "engines": { - "node": ">=8" + "node": ">=16.17" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-filter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", - "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-4.1.0.tgz", + "integrity": "sha512-37/tPdZ3oJwHaS3gNJdenCDB3Tz26i9sjhnguBtvN0vYlRIiDNnvTWkuh+0hETV9rLPdJ3rlL3yVOYPIAnM8rw==", "dev": true, "license": "MIT", "dependencies": { - "p-map": "^2.0.0" + "p-map": "^7.0.1" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-is-promise": { @@ -9565,14 +11129,46 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, "engines": { "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-reduce": { @@ -9585,18 +11181,17 @@ "node": ">=8" } }, - "node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "node_modules/p-timeout": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", + "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", "dev": true, "license": "MIT", - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - }, "engines": { - "node": ">=8" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-try": { @@ -9648,6 +11243,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parse-semver": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/parse-semver/-/parse-semver-1.1.1.tgz", @@ -9659,9 +11267,9 @@ } }, "node_modules/parse-semver/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "license": "ISC", "bin": { @@ -9733,28 +11341,31 @@ "license": "MIT" }, "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", "dev": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } }, "node_modules/path-type": { "version": "4.0.0", @@ -9781,9 +11392,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -9913,60 +11524,14 @@ "node": ">=8" } }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", "dev": true, "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, "engines": { - "node": ">=8" + "node": ">=4" } }, "node_modules/prebuild-install": { @@ -9975,6 +11540,7 @@ "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", @@ -9997,9 +11563,9 @@ } }, "node_modules/pretty-format": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz", - "integrity": "sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10024,6 +11590,22 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/pretty-ms": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -10031,21 +11613,29 @@ "dev": true, "license": "MIT" }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true, + "license": "ISC" + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", "dev": true, "license": "MIT", "engines": { @@ -10069,26 +11659,14 @@ ], "license": "MIT" }, - "node_modules/q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", - "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" - } - }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -10118,26 +11696,6 @@ ], "license": "MIT" }, - "node_modules/quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -10154,6 +11712,19 @@ "rc": "cli.js" } }, + "node_modules/rc-config-loader": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/rc-config-loader/-/rc-config-loader-4.1.4.tgz", + "integrity": "sha512-3GiwEzklkbXTDp52UR5nT8iXgYAx1V9ZG/kDZT7p60u2GCv2XTwQq4NzinMoMpNtXhmt3WkhYXcj6HH8HdwCEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "js-yaml": "^4.1.1", + "json5": "^2.2.3", + "require-from-string": "^2.0.2" + } + }, "node_modules/rc/node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -10184,144 +11755,204 @@ "node": ">=0.8" } }, - "node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "node_modules/read-package-up": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-12.0.0.tgz", + "integrity": "sha512-Q5hMVBYur/eQNWDdbF4/Wqqr9Bjvtrw2kjGxxBbKLbx8bVCL8gcArjTy8zDUuLGQicftpMuU0riQNcAsbtOVsw==", "dev": true, "license": "MIT", "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" + "find-up-simple": "^1.0.1", + "read-pkg": "^10.0.0", + "type-fest": "^5.2.0" }, "engines": { - "node": ">=8" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "node_modules/read-package-up/node_modules/hosted-git-info": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", + "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" + "lru-cache": "^11.1.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "node_modules/read-package-up/node_modules/lru-cache": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, + "license": "BlueOak-1.0.0", "engines": { - "node": ">=8" + "node": "20 || >=22" } }, - "node_modules/read-pkg-up/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "node_modules/read-package-up/node_modules/normalize-package-data": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-8.0.0.tgz", + "integrity": "sha512-RWk+PI433eESQ7ounYxIp67CYuVsS1uYSonX3kA6ps/3LWfjVQa/ptEg6Y3T6uAMq1mWpX9PQ+qx+QaHpsc7gQ==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "dependencies": { - "p-locate": "^4.1.0" + "hosted-git-info": "^9.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" }, "engines": { - "node": ">=8" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/read-pkg-up/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/read-package-up/node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", "dev": true, "license": "MIT", "dependencies": { - "p-try": "^2.0.0" + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" }, "engines": { - "node": ">=6" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/read-pkg-up/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/read-package-up/node_modules/parse-json/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-package-up/node_modules/read-pkg": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-10.1.0.tgz", + "integrity": "sha512-I8g2lArQiP78ll51UeMZojewtYgIRCKCWqZEgOO8c/uefTI+XDXvCSXu3+YNUaTNvZzobrL5+SqHjBrByRRTdg==", "dev": true, "license": "MIT", "dependencies": { - "p-limit": "^2.2.0" + "@types/normalize-package-data": "^2.4.4", + "normalize-package-data": "^8.0.0", + "parse-json": "^8.3.0", + "type-fest": "^5.4.4", + "unicorn-magic": "^0.4.0" }, "engines": { - "node": ">=8" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "node_modules/read-package-up/node_modules/type-fest": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.6.0.tgz", + "integrity": "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==", "dev": true, "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, "engines": { - "node": ">=8" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/read-pkg/node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "node_modules/read-package-up/node_modules/unicorn-magic": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz", + "integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==", "dev": true, - "license": "ISC" + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "node_modules/read-pkg": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", + "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" + "@types/normalize-package-data": "^2.4.3", + "normalize-package-data": "^6.0.0", + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/read-pkg/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "node_modules/read-pkg/node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=8" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/readable-stream": { @@ -10353,47 +11984,33 @@ "node": ">= 10.13.0" } }, - "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "node_modules/registry-auth-token": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.1.tgz", + "integrity": "sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==", "dev": true, "license": "MIT", "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" + "@pnpm/npm-conf": "^3.0.2" }, "engines": { - "node": ">=8" + "node": ">=14" } }, - "node_modules/redeyed": { + "node_modules/require-directory": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", - "integrity": "sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "esprima": "~4.0.0" - } - }, - "node_modules/registry-auth-token": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.2.tgz", - "integrity": "sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg==", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, "license": "MIT", - "dependencies": { - "rc": "1.2.8" - }, "engines": { - "node": ">=6.0.0" + "node": ">=0.10.0" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, "license": "MIT", "engines": { @@ -10431,7 +12048,7 @@ "node": ">=8" } }, - "node_modules/resolve-cwd/node_modules/resolve-from": { + "node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", @@ -10441,30 +12058,10 @@ "node": ">=8" } }, - "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/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, "license": "MIT", "engines": { @@ -10472,43 +12069,17 @@ "node": ">=0.10.0" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, + "license": "MIT", "engines": { - "node": "*" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/run-parallel": { @@ -10535,223 +12106,459 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true, + "license": "ISC" + }, + "node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/secretlint": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/secretlint/-/secretlint-10.2.2.tgz", + "integrity": "sha512-xVpkeHV/aoWe4vP4TansF622nBEImzCY73y/0042DuJ29iKIaqgoJ8fGxre3rVSHHbxar4FdJobmTnLp9AU0eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/config-creator": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/node": "^10.2.2", + "@secretlint/profiler": "^10.2.2", + "debug": "^4.4.1", + "globby": "^14.1.0", + "read-pkg": "^9.0.1" + }, + "bin": { + "secretlint": "bin/secretlint.js" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/semantic-release": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-25.0.3.tgz", + "integrity": "sha512-WRgl5GcypwramYX4HV+eQGzUbD7UUbljVmS+5G1uMwX/wLgYuJAxGeerXJDMO2xshng4+FXqCgyB5QfClV6WjA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@semantic-release/commit-analyzer": "^13.0.1", + "@semantic-release/error": "^4.0.0", + "@semantic-release/github": "^12.0.0", + "@semantic-release/npm": "^13.1.1", + "@semantic-release/release-notes-generator": "^14.1.0", + "aggregate-error": "^5.0.0", + "cosmiconfig": "^9.0.0", + "debug": "^4.0.0", + "env-ci": "^11.0.0", + "execa": "^9.0.0", + "figures": "^6.0.0", + "find-versions": "^6.0.0", + "get-stream": "^6.0.0", + "git-log-parser": "^1.2.0", + "hook-std": "^4.0.0", + "hosted-git-info": "^9.0.0", + "import-from-esm": "^2.0.0", + "lodash-es": "^4.17.21", + "marked": "^15.0.0", + "marked-terminal": "^7.3.0", + "micromatch": "^4.0.2", + "p-each-series": "^3.0.0", + "p-reduce": "^3.0.0", + "read-package-up": "^12.0.0", + "resolve-from": "^5.0.0", + "semver": "^7.3.2", + "signale": "^1.2.1", + "yargs": "^18.0.0" + }, + "bin": { + "semantic-release": "bin/semantic-release.js" + }, + "engines": { + "node": "^22.14.0 || >= 24.10.0" + } + }, + "node_modules/semantic-release/node_modules/@semantic-release/error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/semantic-release/node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/aggregate-error": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", + "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^5.2.0", + "indent-string": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/semantic-release/node_modules/clean-stack": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.3.0.tgz", + "integrity": "sha512-9ngPTOhYGQqNVSfeJkYXHmF7AGWp4/nN5D/QqNQs3Dvxd1Kk/WpjHfNujKHYUQ/5CoGyOyFNoWSPk5afzP0QVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "5.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/semantic-release/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/semantic-release/node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/semantic-release/node_modules/execa/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/hosted-git-info": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", + "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/semantic-release/node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/semantic-release/node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "node_modules/semantic-release/node_modules/lru-cache": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", "dev": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } }, - "node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "node_modules/semantic-release/node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", "dev": true, "license": "MIT", "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" }, "engines": { - "node": ">= 10.13.0" + "node": ">=18" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release": { - "version": "19.0.5", - "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-19.0.5.tgz", - "integrity": "sha512-NMPKdfpXTnPn49FDogMBi36SiBfXkSOJqCkk0E4iWOY1tusvvgBwqUmxTX1kmlT6kIYed9YwNKD1sfPpqa5yaA==", + "node_modules/semantic-release/node_modules/p-reduce": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-3.0.0.tgz", + "integrity": "sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@semantic-release/commit-analyzer": "^9.0.2", - "@semantic-release/error": "^3.0.0", - "@semantic-release/github": "^8.0.0", - "@semantic-release/npm": "^9.0.0", - "@semantic-release/release-notes-generator": "^10.0.0", - "aggregate-error": "^3.0.0", - "cosmiconfig": "^7.0.0", - "debug": "^4.0.0", - "env-ci": "^5.0.0", - "execa": "^5.0.0", - "figures": "^3.0.0", - "find-versions": "^4.0.0", - "get-stream": "^6.0.0", - "git-log-parser": "^1.2.0", - "hook-std": "^2.0.0", - "hosted-git-info": "^4.0.0", - "lodash": "^4.17.21", - "marked": "^4.0.10", - "marked-terminal": "^5.0.0", - "micromatch": "^4.0.2", - "p-each-series": "^2.1.0", - "p-reduce": "^2.0.0", - "read-pkg-up": "^7.0.0", - "resolve-from": "^5.0.0", - "semver": "^7.3.2", - "semver-diff": "^3.1.1", - "signale": "^1.2.1", - "yargs": "^16.2.0" - }, - "bin": { - "semantic-release": "bin/semantic-release.js" - }, "engines": { - "node": ">=16 || ^14.17" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "node_modules/semantic-release/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release/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/semantic-release/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "node_modules/semantic-release/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "license": "MIT", + "license": "ISC", "engines": { - "node": ">=8" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/semantic-release/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==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/semantic-release/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==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/semantic-release/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", "dev": true, "license": "MIT", "dependencies": { - "cliui": "^7.0.2", + "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^7.2.0", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^22.0.0" }, "engines": { - "node": ">=10" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, - "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "node_modules/semantic-release/node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", "dev": true, "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, "engines": { - "node": ">=10" - } - }, - "node_modules/semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, - "node_modules/semver-diff/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/semver-regex": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.4.tgz", - "integrity": "sha512-6IiqeZNgq01qGf0TId0t3NvKzSvUsjcpdEO3AQNeIjR6A2+ckTnQlDpl4qu1bjRv0RzN3FP9hzFmws3lKqRWkA==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz", + "integrity": "sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -10789,15 +12596,76 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "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.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "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-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "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" @@ -10935,7 +12803,8 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/simple-get": { "version": "4.0.1", @@ -10957,12 +12826,26 @@ } ], "license": "MIT", + "optional": true, "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, + "node_modules/skin-tone": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", + "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-emoji-modifier-base": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -10973,6 +12856,24 @@ "node": ">=8" } }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -11001,9 +12902,9 @@ "license": "MIT" }, "node_modules/spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11012,68 +12913,30 @@ } }, "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", "dev": true, "license": "CC-BY-3.0" - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", - "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "through": "2" - }, - "engines": { - "node": "*" - } - }, - "node_modules/split2": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", - "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", - "dev": true, - "license": "ISC", - "dependencies": { - "readable-stream": "^3.0.0" - } - }, - "node_modules/split2/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, + "node_modules/spdx-license-ids": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -11139,26 +13002,30 @@ "node": ">=10" } }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "node_modules/string-length/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, - "node_modules/string-width-cjs": { - "name": "string-width", + "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==", @@ -11173,57 +13040,33 @@ "node": ">=8" } }, - "node_modules/string-width-cjs/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/string-width/node_modules/ansi-regex": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", - "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "node_modules/string-width-cjs": { + "name": "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": { - "ansi-regex": "^6.0.1" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": ">=8" } }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/string-width-cjs/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", - "dependencies": { - "ansi-regex": "^5.0.1" - }, "engines": { "node": ">=8" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", + "node_modules/string-width-cjs/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==", @@ -11236,399 +13079,295 @@ "node": ">=8" } }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "node_modules/string-width/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/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "node_modules/string-width/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": { - "min-indent": "^1.0.0" + "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { + "node_modules/strip-ansi": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "ansi-regex": "^6.2.2" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "node_modules/strip-ansi-cjs": { + "name": "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": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" + "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" } }, - "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==", + "node_modules/strip-ansi-cjs/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": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/synckit": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", - "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.2.9" - }, "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/synckit" + "node": ">=8" } }, - "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", - "dev": true, - "license": "MIT", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "license": "MIT", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, "engines": { - "node": ">=6" - } - }, - "node_modules/tar-stream/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "node": ">=8" }, - "engines": { - "node": ">= 6" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/temp-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", - "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "node_modules/structured-source": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/structured-source/-/structured-source-4.0.0.tgz", + "integrity": "sha512-qGzRFNJDjFieQkl/sVOI2dUjHKRyL9dAJi2gCPGJLbJHBIkyOHxjuocpIEfbLioX+qSJpvbYdT49/YCdMznKxA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "license": "BSD-2-Clause", + "dependencies": { + "boundary": "^2.0.0" } }, - "node_modules/tempy": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-1.0.1.tgz", - "integrity": "sha512-biM9brNqxSc04Ee71hzFbryD11nX7VPhQQY32AdDmjFvodsRFz/3ufeoTZ6uYkRFfGo188tENcASNs3vTdsM0w==", + "node_modules/super-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.1.0.tgz", + "integrity": "sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ==", "dev": true, "license": "MIT", "dependencies": { - "del": "^6.0.0", - "is-stream": "^2.0.0", - "temp-dir": "^2.0.0", - "type-fest": "^0.16.0", - "unique-string": "^2.0.0" + "function-timeout": "^1.0.1", + "make-asynchronous": "^1.0.1", + "time-span": "^5.1.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tempy/node_modules/type-fest": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", - "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/terser": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.9.0.tgz", - "integrity": "sha512-h5hxa23sCdpzcye/7b8YqbE5OwKca/ni0RQz1uRX3tGh8haaGHqcuSqbGRybuAKNdntZ0mDgFNXPJ48xQ2RXKQ==", + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/terser-webpack-plugin": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.2.4.tgz", - "integrity": "sha512-E2CkNMN+1cho04YpdANyRrn8CyN4yMy+WdFKZIySFZrGXZxJwJP6PMNGGc/Mcr6qygQHUUqRxnAPmi0M9f00XA==", + "node_modules/supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", "dev": true, "license": "MIT", "dependencies": { - "jest-worker": "^27.0.6", - "p-limit": "^3.1.0", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "source-map": "^0.6.1", - "terser": "^5.7.2" + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" }, "engines": { - "node": ">= 10.13.0" + "node": ">=14.18" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" } }, - "node_modules/terser-webpack-plugin/node_modules/jest-worker": { - "version": "27.3.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.3.0.tgz", - "integrity": "sha512-xTTvvJqOjKBqE1AmwDHiQN8qzp9VoT981LtfXA+XiJVxHn4435vpnrzVcJ6v/ESiuB+IXPjZakn/ppT00xBCWA==", + "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", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, "engines": { - "node": ">= 10.13.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/terser-webpack-plugin/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/synckit": { + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@pkgr/core": "^0.2.9" }, "engines": { - "node": ">=10" + "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "url": "https://opencollective.com/synckit" } }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/terser/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "node_modules/table": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", "dev": true, "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, "engines": { - "node": ">= 8" + "node": ">=10.0.0" } }, - "node_modules/terser/node_modules/source-map-support": { - "version": "0.5.20", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", - "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", + "node_modules/table/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", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/terser/node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "node_modules/table/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": "ISC", + "license": "MIT", "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" + "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" } }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, + "license": "MIT", "engines": { - "node": "*" + "node": ">=20" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/text-extensions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", - "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==", + "node_modules/tapable": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz", + "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10" + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } }, - "node_modules/through2": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", - "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "readable-stream": "3" + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" } }, - "node_modules/through2/node_modules/readable-stream": { + "node_modules/tar-stream/node_modules/readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -11638,705 +13377,782 @@ "node": ">= 6" } }, - "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "node_modules/temp-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", + "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==", "dev": true, "license": "MIT", - "dependencies": { - "rimraf": "^3.0.0" - }, "engines": { - "node": ">=8.17.0" + "node": ">=14.16" } }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true, - "license": "BSD-3-Clause" - }, - "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==", + "node_modules/tempy": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.2.0.tgz", + "integrity": "sha512-d79HhZya5Djd7am0q+W4RTsSU+D/aJzM+4Y4AGJGuGlgM2L6sx5ZvOYTmZjqPhrDrV6xJTtRSm1JCLj6V6LHLQ==", "dev": true, "license": "MIT", "dependencies": { - "is-number": "^7.0.0" + "is-stream": "^3.0.0", + "temp-dir": "^3.0.0", + "type-fest": "^2.12.2", + "unique-string": "^3.0.0" }, "engines": { - "node": ">=8.0" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true, - "license": "MIT" - }, - "node_modules/traverse": { - "version": "0.6.7", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.7.tgz", - "integrity": "sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==", + "node_modules/tempy/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", "dev": true, "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/trim-newlines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "node_modules/tempy/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true, - "license": "MIT", + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=8" + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ts-jest": { - "version": "29.4.1", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.1.tgz", - "integrity": "sha512-SaeUtjfpg9Uqu8IbeDKtdaS0g8lS6FT6OzM3ezrDfErPJPHNDo/Ey+VFGP1bQIDfagYDLyRpd7O15XpG1Es2Uw==", + "node_modules/terminal-link": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-4.0.0.tgz", + "integrity": "sha512-lk+vH+MccxNqgVqSnkMVKx4VLJfnLjDBGzH16JVZjKE2DoxP57s6/vt6JmXV5I3jBcfGrxNrYtC+mPtU7WJztA==", "dev": true, "license": "MIT", "dependencies": { - "bs-logger": "^0.2.6", - "fast-json-stable-stringify": "^2.1.0", - "handlebars": "^4.7.8", - "json5": "^2.2.3", - "lodash.memoize": "^4.1.2", - "make-error": "^1.3.6", - "semver": "^7.7.2", - "type-fest": "^4.41.0", - "yargs-parser": "^21.1.1" - }, - "bin": { - "ts-jest": "cli.js" + "ansi-escapes": "^7.0.0", + "supports-hyperlinks": "^3.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/transform": "^29.0.0 || ^30.0.0", - "@jest/types": "^29.0.0 || ^30.0.0", - "babel-jest": "^29.0.0 || ^30.0.0", - "jest": "^29.0.0 || ^30.0.0", - "jest-util": "^29.0.0 || ^30.0.0", - "typescript": ">=4.3 <6" + "node": ">=18" }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/transform": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jest-util": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ts-jest/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "node_modules/terminal-link/node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", "dev": true, - "license": "(MIT OR CC0-1.0)", + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, "engines": { - "node": ">=16" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ts-jest/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "node_modules/terser": { + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz", + "integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==", "dev": true, - "license": "ISC", + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, "engines": { - "node": ">=12" + "node": ">=10" } }, - "node_modules/ts-loader": { - "version": "9.4.2", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz", - "integrity": "sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==", + "node_modules/terser-webpack-plugin": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", + "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4" + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" }, "engines": { - "node": ">=12.0.0" + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "typescript": "*", - "webpack": "^5.0.0" + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } } }, - "node_modules/tunnel": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", - "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "node_modules/terser-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, "engines": { - "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + "node": ">= 10.13.0" } }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "node_modules/terser-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "safe-buffer": "^5.0.1" + "has-flag": "^4.0.0" }, "engines": { - "node": "*" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } + "license": "MIT" }, - "node_modules/typed-rest-client": { - "version": "1.8.9", - "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.9.tgz", - "integrity": "sha512-uSmjE38B80wjL85UFX3sTYEUlvZ1JgCRhsWj/fJ4rZ0FqDUFoIuodtiVeE+cUqiVTOKPdKrp/sdftD15MDek6g==", + "node_modules/terser/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "license": "MIT", "dependencies": { - "qs": "^6.9.1", - "tunnel": "0.0.6", - "underscore": "^1.12.1" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, - "node_modules/typescript": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", - "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, - "license": "Apache-2.0", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" }, "engines": { - "node": ">=4.2.0" + "node": ">=8" } }, - "node_modules/uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, - "license": "MIT" - }, - "node_modules/uglify-js": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.14.2.tgz", - "integrity": "sha512-rtPMlmcO4agTUfz10CbgJ1k6UAoXM2gWb3GoMPPZB/+/Ackf8lNWk11K4rYi2D0apgoFRLtQOZhb+/iGNJq26A==", - "license": "BSD-2-Clause", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">=0.8.0" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/underscore": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", - "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==", + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true, "license": "MIT" }, - "node_modules/unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "node_modules/textextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-6.11.0.tgz", + "integrity": "sha512-tXJwSr9355kFJI3lbCkPpUH5cP8/M0GGy2xLO34aZCjMXBaK3SoPnZwr/oWmo1FdCnELcs4npdCIOFtq9W3ruQ==", "dev": true, - "license": "MIT", + "license": "Artistic-2.0", "dependencies": { - "crypto-random-string": "^2.0.0" + "editions": "^6.21.0" }, "engines": { - "node": ">=8" + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" } }, - "node_modules/universal-user-agent": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", - "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==", + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", "dev": true, - "license": "ISC" + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } }, - "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", "dev": true, "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, "engines": { - "node": ">= 10.0.0" + "node": ">=0.8" } }, - "node_modules/unrs-resolver": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", - "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "node_modules/time-span": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", + "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==", "dev": true, - "hasInstallScript": true, "license": "MIT", "dependencies": { - "napi-postinstall": "^0.3.0" + "convert-hrtime": "^5.0.0" }, - "funding": { - "url": "https://opencollective.com/unrs-resolver" + "engines": { + "node": ">=12" }, - "optionalDependencies": { - "@unrs/resolver-binding-android-arm-eabi": "1.11.1", - "@unrs/resolver-binding-android-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-x64": "1.11.1", - "@unrs/resolver-binding-freebsd-x64": "1.11.1", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", - "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-musl": "1.11.1", - "@unrs/resolver-binding-wasm32-wasi": "1.11.1", - "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", - "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", - "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" }, - "bin": { - "update-browserslist-db": "cli.js" + "engines": { + "node": ">=12.0.0" }, - "peerDependencies": { - "browserslist": ">= 4.21.0" + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/url-join": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", - "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=14.14" + } }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "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": "ISC", + "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" + "is-number": "^7.0.0" }, "engines": { - "node": ">=10.12.0" + "node": ">=8.0" } }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "node_modules/traverse": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.7.tgz", + "integrity": "sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/vsce": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/vsce/-/vsce-2.15.0.tgz", - "integrity": "sha512-P8E9LAZvBCQnoGoizw65JfGvyMqNGlHdlUXD1VAuxtvYAaHBKLBdKPnpy60XKVDAkQCfmMu53g+gq9FM+ydepw==", - "deprecated": "vsce has been renamed to @vscode/vsce. Install using @vscode/vsce instead.", + "node_modules/ts-jest": { + "version": "29.4.9", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.9.tgz", + "integrity": "sha512-LTb9496gYPMCqjeDLdPrKuXtncudeV1yRZnF4Wo5l3SFi0RYEnYRNgMrFIdg+FHvfzjCyQk1cLncWVqiSX+EvQ==", "dev": true, "license": "MIT", "dependencies": { - "azure-devops-node-api": "^11.0.1", - "chalk": "^2.4.2", - "cheerio": "^1.0.0-rc.9", - "commander": "^6.1.0", - "glob": "^7.0.6", - "hosted-git-info": "^4.0.2", - "keytar": "^7.7.0", - "leven": "^3.1.0", - "markdown-it": "^12.3.2", - "mime": "^1.3.4", - "minimatch": "^3.0.3", - "parse-semver": "^1.1.1", - "read": "^1.0.7", - "semver": "^5.1.0", - "tmp": "^0.2.1", - "typed-rest-client": "^1.8.4", - "url-join": "^4.0.1", - "xml2js": "^0.4.23", - "yauzl": "^2.3.1", - "yazl": "^2.2.2" + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.9", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.4", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" }, "bin": { - "vsce": "vsce" + "ts-jest": "cli.js" }, "engines": { - "node": ">= 14" + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <7" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } } }, - "node_modules/vsce/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=4" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/vsce/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/ts-loader": { + "version": "9.5.7", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.7.tgz", + "integrity": "sha512-/ZNrKgA3K3PtpMYOC71EeMWIloGw3IYEa5/t1cyz2r5/PyUwTXGzYJvcD3kfUvmhlfpz1rhV8B2O6IVTQ0avsg==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4", + "source-map": "^0.7.4" }, "engines": { - "node": ">=4" + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" } }, - "node_modules/vsce/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "node_modules/ts-loader/node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" } }, - "node_modules/vsce/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "license": "MIT" + "license": "0BSD" }, - "node_modules/vsce/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.8.0" + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" } }, - "node_modules/vsce/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "dev": true, - "license": "ISC", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "safe-buffer": "^5.0.1" }, "engines": { "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/vsce/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0= sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true, "license": "MIT", "engines": { "node": ">=4" } }, - "node_modules/vsce/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-rest-client": { + "version": "1.8.9", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.9.tgz", + "integrity": "sha512-uSmjE38B80wjL85UFX3sTYEUlvZ1JgCRhsWj/fJ4rZ0FqDUFoIuodtiVeE+cUqiVTOKPdKrp/sdftD15MDek6g==", "dev": true, "license": "MIT", + "dependencies": { + "qs": "^6.9.1", + "tunnel": "0.0.6", + "underscore": "^1.12.1" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, "bin": { - "mime": "cli.js" + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4" + "node": ">=14.17" } }, - "node_modules/vsce/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "license": "ISC", + "node_modules/uglify-js": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.14.2.tgz", + "integrity": "sha512-rtPMlmcO4agTUfz10CbgJ1k6UAoXM2gWb3GoMPPZB/+/Ackf8lNWk11K4rYi2D0apgoFRLtQOZhb+/iGNJq26A==", + "license": "BSD-2-Clause", + "optional": true, "bin": { - "semver": "bin/semver" + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" } }, - "node_modules/vsce/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/underscore": { + "version": "1.13.8", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", + "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", + "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", "dev": true, "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, "engines": { - "node": ">=4" + "node": ">=20.18.1" } }, - "node_modules/vscode": { - "version": "1.1.37", - "resolved": "https://registry.npmjs.org/vscode/-/vscode-1.1.37.tgz", - "integrity": "sha512-vJNj6IlN7IJPdMavlQa1KoFB3Ihn06q1AiN3ZFI/HfzPNzbKZWPPuiU+XkpNOfGU5k15m4r80nxNPlM7wcc0wg==", - "deprecated": "This package is deprecated in favor of @types/vscode and vscode-test. For more information please read: https://code.visualstudio.com/updates/v1_36#_splitting-vscode-package-into-typesvscode-and-vscodetest", + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicode-emoji-modifier-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", + "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", "dev": true, "license": "MIT", - "dependencies": { - "glob": "^7.1.2", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "mocha": "^5.2.0", - "semver": "^5.4.1", - "source-map-support": "^0.5.0", - "vscode-test": "^0.4.1" - }, - "bin": { - "vscode-install": "bin/install" - }, "engines": { - "node": ">=8.9.3" + "node": ">=4" } }, - "node_modules/vscode-test": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/vscode-test/-/vscode-test-0.4.3.tgz", - "integrity": "sha512-EkMGqBSefZH2MgW65nY05rdRSko15uvzq4VAPM5jVmwYuFQKE7eikKXNJDRxL+OITXHB6pI+a3XqqD32Y3KC5w==", - "deprecated": "This package has been renamed to @vscode/test-electron, please update to the new name", + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", "dev": true, "license": "MIT", - "dependencies": { - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.1" - }, "engines": { - "node": ">=8.9.3" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/vscode-test/node_modules/agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "node_modules/unique-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", + "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", "dev": true, "license": "MIT", "dependencies": { - "es6-promisify": "^5.0.0" + "crypto-random-string": "^4.0.0" }, "engines": { - "node": ">= 4.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/vscode-test/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "node_modules/universal-user-agent": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", + "dev": true, + "license": "ISC" + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true, "license": "MIT", - "dependencies": { - "ms": "2.0.0" + "engines": { + "node": ">= 10.0.0" } }, - "node_modules/vscode-test/node_modules/http-proxy-agent": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", - "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", "dev": true, + "hasInstallScript": true, "license": "MIT", "dependencies": { - "agent-base": "4", - "debug": "3.1.0" + "napi-postinstall": "^0.3.0" }, - "engines": { - "node": ">= 4.5.0" + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, - "node_modules/vscode-test/node_modules/https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, - "engines": { - "node": ">= 4.5.0" + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, - "node_modules/vscode-test/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", "dev": true, "license": "MIT" }, - "node_modules/vscode/node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 6" + "bin": { + "uuid": "dist/bin/uuid" } }, - "node_modules/vscode/node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" }, "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=10.12.0" } }, - "node_modules/vscode/node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" } }, - "node_modules/vscode/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "node_modules/version-range": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.15.0.tgz", + "integrity": "sha512-Ck0EJbAGxHwprkzFO966t4/5QkRuzh+/I1RxhLgUKKwEn+Cd8NwM60mE3AqBZg5gYODoXW0EFsQvbZjRlvdqbg==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" + "license": "Artistic-2.0", + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" } }, "node_modules/walker": { @@ -12350,9 +14166,9 @@ } }, "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", "dev": true, "license": "MIT", "dependencies": { @@ -12363,45 +14179,45 @@ "node": ">=10.13.0" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "node_modules/web-worker": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz", + "integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==", "dev": true, - "license": "BSD-2-Clause" + "license": "Apache-2.0" }, "node_modules/webpack": { - "version": "5.75.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz", - "integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==", + "version": "5.106.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.106.2.tgz", + "integrity": "sha512-wGN3qcrBQIFmQ/c0AiOAQBvrZ5lmY8vbbMv4Mxfgzqd/B6+9pXtLo73WuS1dSGXM5QYY3hZnIbvx+K1xxe6FyA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.16.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", + "enhanced-resolve": "^5.20.0", + "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", + "graceful-fs": "^4.2.11", + "loader-runner": "^4.3.1", + "mime-db": "^1.54.0", "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.17", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.4" }, "bin": { "webpack": "bin/webpack.js" @@ -12420,44 +14236,38 @@ } }, "node_modules/webpack-cli": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.0.1.tgz", - "integrity": "sha512-S3KVAyfwUqr0Mo/ur3NzIp6jnerNpo7GUO6so51mxLi1spqsA17YcMXy0WOIJtBSnj748lthxC6XLbNKh/ZC+A==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-7.0.2.tgz", + "integrity": "sha512-dB0R4T+C/8YuvM+fabdvil6QE44/ChDXikV5lOOkrUeCkW5hTJv2pGLE3keh+D5hjYw8icBaJkZzpFoaHV4T+g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.0.1", - "@webpack-cli/info": "^2.0.1", - "@webpack-cli/serve": "^2.0.1", - "colorette": "^2.0.14", - "commander": "^9.4.1", - "cross-spawn": "^7.0.3", - "envinfo": "^7.7.3", + "@discoveryjs/json-ext": "^1.0.0", + "commander": "^14.0.3", + "cross-spawn": "^7.0.6", + "envinfo": "^7.14.0", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", "interpret": "^3.1.1", "rechoir": "^0.8.0", - "webpack-merge": "^5.7.3" + "webpack-merge": "^6.0.1" }, "bin": { "webpack-cli": "bin/cli.js" }, "engines": { - "node": ">=14.15.0" + "node": ">=20.9.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "5.x.x" + "webpack": "^5.101.0", + "webpack-bundle-analyzer": "^4.0.0 || ^5.0.0", + "webpack-dev-server": "^5.0.0" }, "peerDependenciesMeta": { - "@webpack-cli/generators": { - "optional": true - }, "webpack-bundle-analyzer": { "optional": true }, @@ -12467,48 +14277,48 @@ } }, "node_modules/webpack-cli/node_modules/commander": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.1.tgz", - "integrity": "sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", "dev": true, "license": "MIT", "engines": { - "node": "^12.20.0 || >=14" + "node": ">=20" } }, "node_modules/webpack-merge": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", - "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", "dev": true, "license": "MIT", "dependencies": { "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" + "flat": "^5.0.2", + "wildcard": "^2.0.1" }, "engines": { - "node": ">=10.0.0" + "node": ">=18.0.0" } }, "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", + "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", "dev": true, "license": "MIT", "engines": { "node": ">=10.13.0" } }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "node_modules/webpack/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "dev": true, "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" + "engines": { + "node": ">= 0.6" } }, "node_modules/which": { @@ -12528,9 +14338,9 @@ } }, "node_modules/wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", "dev": true, "license": "MIT" }, @@ -12541,18 +14351,18 @@ "license": "MIT" }, "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "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": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" @@ -12577,68 +14387,50 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi-cjs/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/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/wrap-ansi-cjs/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", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, "engines": { "node": ">=8" } }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", - "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", + "node_modules/wrap-ansi-cjs/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", - "engines": { - "node": ">=12" + "dependencies": { + "ansi-regex": "^5.0.1" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "engines": { + "node": ">=8" } }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "node_modules/wrap-ansi/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": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=8" } }, "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "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": "^6.0.1" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": ">=8" } }, "node_modules/wrappy": { @@ -12675,18 +14467,20 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", "dev": true, "license": "MIT", "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" + "is-wsl": "^3.1.0" }, "engines": { - "node": ">=4.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/xmlbuilder": { @@ -12726,16 +14520,6 @@ "dev": true, "license": "ISC" }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 6" - } - }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -12756,38 +14540,6 @@ } }, "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs/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/yargs/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/yargs/node_modules/yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", @@ -12797,17 +14549,6 @@ "node": ">=12" } }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, "node_modules/yazl": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", @@ -12830,6 +14571,19 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index a6da8fa..0e173d8 100644 --- a/package.json +++ b/package.json @@ -52,38 +52,36 @@ "watch": "webpack --watch", "package": "webpack --mode production --devtool hidden-source-map", "build": "vsce package", + "lint": "biome check --error-on-warnings . --write", "lint:check": "biome check --error-on-warnings .", - "lint:fix": "biome check --error-on-warnings . --write", "test": "jest", "test:watch": "jest --watch", "test:coverage": "jest --coverage", "semantic-release": "semantic-release", - "dev-install": "npm run compile && npx vsce package && code --install-extension codebot-1.0.0.vsix --force" + "dev-install": "npm run compile && vsce package && code --install-extension codebot-$(node -p \"require('./package.json').version\").vsix --force" }, "engines": { - "vscode": "^1.74.0", + "vscode": "^1.100.0", "node": ">=24.12.0" }, "dependencies": { - "fs": "^0.0.2", - "handlebars": "^4.7.7" + "handlebars": "^4.7.9" }, "devDependencies": { - "@semantic-release/changelog": "^6.0.2", + "@biomejs/biome": "^2.4.12", + "@semantic-release/changelog": "^6.0.3", "@semantic-release/git": "^10.0.1", "@types/jest": "^30.0.0", - "@types/node": "18.x", - "@types/vscode": "^1.74.0", - "@biomejs/biome": "^1.9.4", - "git-commit-msg-linter": "^4.5.0", - "jest": "^30.1.1", - "semantic-release": "^19.0.5", - "ts-jest": "^29.4.1", - "ts-loader": "^9.4.2", - "typescript": "^4.9.4", - "vsce": "^2.15.0", - "vscode": "^1.1.37", - "webpack": "^5.75.0", - "webpack-cli": "^5.0.1" + "@types/node": "24.x", + "@types/vscode": "^1.100.0", + "@vscode/vsce": "^3.9.0", + "git-commit-msg-linter": "^5.0.8", + "jest": "^30.3.0", + "semantic-release": "^25.0.3", + "ts-jest": "^29.4.9", + "ts-loader": "^9.5.7", + "typescript": "^5.8.2", + "webpack": "^5.106.2", + "webpack-cli": "^7.0.2" } } diff --git a/src/commands/createComponent.ts b/src/commands/createComponent.ts index b81b33d..7056ded 100644 --- a/src/commands/createComponent.ts +++ b/src/commands/createComponent.ts @@ -1,25 +1,30 @@ -import * as path from 'path'; -import { ProjectDetector } from '../managers/ProjectDetector'; -import { TemplateManager } from '../managers/TemplateManager'; -import { PathResolver } from '../utils/PathResolver'; -import { ConfigurationManager } from '../managers/ConfigurationManager'; -import { ErrorType, CommandArgs, TemplateType, ProcessedFile, ProcessedDirectory } from '../types'; +import * as path from 'node:path'; import { CodebotError } from '../errors'; import { + checkExistsFile, + createFile, + createFolder, getTextByInputBox, showMessage, showSearchDropdown, - createFolder, - createFile, - checkExistsFile, - formatTemplateName, } from '../helpers'; +import { ConfigurationManager } from '../managers/ConfigurationManager'; +import { ProjectDetector } from '../managers/ProjectDetector'; +import { TemplateManager } from '../managers/TemplateManager'; +import { + type CommandArgs, + ErrorType, + type ProcessedDirectory, + type ProcessedFile, + type TemplateType, +} from '../types'; +import { PathResolver } from '../utils/PathResolver'; export async function createComponent(args: CommandArgs) { const projectDetector = new ProjectDetector(); const templateManager = new TemplateManager(); const pathResolver = new PathResolver(); - const configurationManager = new ConfigurationManager(); + const _configurationManager = new ConfigurationManager(); try { // Step 1: Detect project context using ProjectDetector @@ -29,24 +34,27 @@ export async function createComponent(args: CommandArgs) { ErrorType.WORKSPACE_NOT_FOUND, 'No folder path provided for component creation', { args }, - false + false, ); } - const projectContext = await projectDetector.detectProject(currentFolderPath); + const projectContext = + await projectDetector.detectProject(currentFolderPath); // Step 2: Get component name from user - const componentNameInput = await getTextByInputBox('Enter the component name:'); + const componentNameInput = await getTextByInputBox( + 'Enter the component name:', + ); if (!componentNameInput || componentNameInput.trim() === '') { throw new CodebotError( ErrorType.INVALID_COMPONENT_NAME, 'Component name cannot be empty', { componentName: componentNameInput }, - true + true, ); } - const trimmedComponentName = componentNameInput!.trim(); + const trimmedComponentName = componentNameInput?.trim(); // Step 3: Validate component name using PathResolver if (!pathResolver.validatePath(trimmedComponentName)) { @@ -54,23 +62,25 @@ export async function createComponent(args: CommandArgs) { ErrorType.INVALID_COMPONENT_NAME, `Invalid component name: ${trimmedComponentName}. Component names must be valid file/folder names.`, { componentName: trimmedComponentName }, - true + true, ); } // Step 4: Discover templates using TemplateManager with hierarchical discovery - const availableTemplates = await templateManager.discoverTemplates(projectContext.projectRoot); - + const availableTemplates = await templateManager.discoverTemplates( + projectContext.projectRoot, + ); + if (availableTemplates.length === 0) { throw new CodebotError( ErrorType.TEMPLATE_FOLDER_EMPTY, `No templates found in project. Searched in: ${projectContext.templatePath}`, - { + { projectRoot: projectContext.projectRoot, templatePath: projectContext.templatePath, - isMultiProject: projectContext.isMultiProject + isMultiProject: projectContext.isMultiProject, }, - true + true, ); } @@ -92,11 +102,13 @@ export async function createComponent(args: CommandArgs) { ErrorType.TEMPLATE_NOT_FOUND, 'No template type selected', { availableTemplates: templateNames }, - true + true, ); } - selectedTemplate = availableTemplates.find(t => t.name === selectedTemplateName); + selectedTemplate = availableTemplates.find( + t => t.name === selectedTemplateName, + ); } if (!selectedTemplate) { @@ -104,22 +116,34 @@ export async function createComponent(args: CommandArgs) { ErrorType.TEMPLATE_NOT_FOUND, 'Selected template not found', { availableTemplates }, - false + false, ); } // Step 6: Resolve target path using PathResolver - const targetBasePath = pathResolver.resolveTargetPath(projectContext, trimmedComponentName, currentFolderPath); - + const targetBasePath = pathResolver.resolveTargetPath( + projectContext, + trimmedComponentName, + currentFolderPath, + ); + // Step 7: Process templates hierarchically and create files - const templateStructure = await templateManager.scanTemplateStructure(selectedTemplate.path); - - if (templateStructure.files.length === 0 && templateStructure.directories.length === 0) { + const templateStructure = await templateManager.scanTemplateStructure( + selectedTemplate.path, + ); + + if ( + templateStructure.files.length === 0 && + templateStructure.directories.length === 0 + ) { throw new CodebotError( ErrorType.TEMPLATE_FOLDER_EMPTY, `Template folder is empty: ${selectedTemplate.path}`, - { templatePath: selectedTemplate.path, templateName: selectedTemplate.name }, - false + { + templatePath: selectedTemplate.path, + templateName: selectedTemplate.name, + }, + false, ); } @@ -129,25 +153,32 @@ export async function createComponent(args: CommandArgs) { } // Process the template hierarchy - const processedTemplate = await templateManager.processTemplateHierarchy(templateStructure, trimmedComponentName); - + const processedTemplate = await templateManager.processTemplateHierarchy( + templateStructure, + trimmedComponentName, + ); + // Track creation and skipping statistics const creationReport = { filesCreated: 0, filesSkipped: 0, directoriesCreated: 0, createdFiles: [] as string[], - skippedFiles: [] as string[] + skippedFiles: [] as string[], }; // Process root-level files await processFiles(processedTemplate.files, targetBasePath, creationReport); - + // Process directories recursively - await processDirectories(processedTemplate.directories, targetBasePath, creationReport); + await processDirectories( + processedTemplate.directories, + targetBasePath, + creationReport, + ); - let filesCreated = creationReport.filesCreated; - let filesSkipped = creationReport.filesSkipped; + const filesCreated = creationReport.filesCreated; + const filesSkipped = creationReport.filesSkipped; // Step 8: Show success message with details let successMessage = `Component '${trimmedComponentName}' created successfully!`; @@ -167,21 +198,32 @@ export async function createComponent(args: CommandArgs) { } showMessage(successMessage, 'information'); - } catch (error) { // Handle multi-project specific errors if (error instanceof CodebotError) { let errorMessage = error.message; - + // Add context for multi-project scenarios - if (error.type === ErrorType.TEMPLATE_FOLDER_EMPTY && error.details?.isMultiProject) { - errorMessage += '\n\nTip: In multi-project workspaces, templates can be placed in:'; - errorMessage += `\n- Project-specific: ${error.details.projectRoot}/templates`; - errorMessage += `\n- Workspace-wide: ${error.details.workspaceRoot}/templates`; + const details = error.details as + | { + isMultiProject?: boolean; + projectRoot?: string; + workspaceRoot?: string; + } + | undefined; + if ( + error.type === ErrorType.TEMPLATE_FOLDER_EMPTY && + details?.isMultiProject + ) { + errorMessage += + '\n\nTip: In multi-project workspaces, templates can be placed in:'; + errorMessage += `\n- Project-specific: ${details.projectRoot}/templates`; + errorMessage += `\n- Workspace-wide: ${details.workspaceRoot}/templates`; } - + if (error.type === ErrorType.PROJECT_DETECTION_ERROR) { - errorMessage += '\n\nTip: Ensure you are running the command from within a valid project folder.'; + errorMessage += + '\n\nTip: Ensure you are running the command from within a valid project folder.'; } showMessage(errorMessage, 'error'); @@ -191,28 +233,31 @@ export async function createComponent(args: CommandArgs) { ErrorType.FILE_SYSTEM_ERROR, `Unexpected error during component creation: ${error.message}`, { originalError: error.message, stack: error.stack }, - false + false, ); - + showMessage(unexpectedError.message, 'error'); } else { // Handle unknown errors - showMessage('An unknown error occurred during component creation', 'error'); + showMessage( + 'An unknown error occurred during component creation', + 'error', + ); } } } // Helper function to process files hierarchically async function processFiles( - files: ProcessedFile[], - basePath: string, + files: ProcessedFile[], + basePath: string, creationReport: { filesCreated: number; filesSkipped: number; directoriesCreated: number; createdFiles: string[]; skippedFiles: string[]; - } + }, ): Promise { for (const file of files) { const targetFilePath = path.join(basePath, file.targetPath); @@ -234,7 +279,7 @@ async function processFiles( ErrorType.TEMPLATE_PROCESSING_ERROR, `Failed to create file: ${file.targetPath}`, { targetPath: targetFilePath, error }, - false + false, ); } } @@ -242,15 +287,15 @@ async function processFiles( // Helper function to process directories hierarchically async function processDirectories( - directories: ProcessedDirectory[], - basePath: string, + directories: ProcessedDirectory[], + basePath: string, creationReport: { filesCreated: number; filesSkipped: number; directoriesCreated: number; createdFiles: string[]; skippedFiles: string[]; - } + }, ): Promise { for (const directory of directories) { const targetDirPath = path.join(basePath, directory.targetPath); @@ -263,8 +308,12 @@ async function processDirectories( // Process files in this directory await processFiles(directory.files, basePath, creationReport); - + // Process subdirectories recursively - await processDirectories(directory.subdirectories, basePath, creationReport); + await processDirectories( + directory.subdirectories, + basePath, + creationReport, + ); } } diff --git a/src/commands/updateComponent.ts b/src/commands/updateComponent.ts index 59cd2e4..1abe5b2 100644 --- a/src/commands/updateComponent.ts +++ b/src/commands/updateComponent.ts @@ -1,24 +1,29 @@ -import * as path from 'path'; -import { ProjectDetector } from '../managers/ProjectDetector'; -import { TemplateManager } from '../managers/TemplateManager'; -import { PathResolver } from '../utils/PathResolver'; -import { ConfigurationManager } from '../managers/ConfigurationManager'; -import { ErrorType, CommandArgs, TemplateType, ProcessedFile, ProcessedDirectory } from '../types'; +import * as path from 'node:path'; import { CodebotError } from '../errors'; import { + checkExistsFile, + createFile, + createFolder, showMessage, showSearchDropdown, - createFolder, - createFile, - checkExistsFile, - formatTemplateName, } from '../helpers'; +import { ConfigurationManager } from '../managers/ConfigurationManager'; +import { ProjectDetector } from '../managers/ProjectDetector'; +import { TemplateManager } from '../managers/TemplateManager'; +import { + type CommandArgs, + ErrorType, + type ProcessedDirectory, + type ProcessedFile, + type TemplateType, +} from '../types'; +import { PathResolver } from '../utils/PathResolver'; export async function updateComponent(args: CommandArgs) { const projectDetector = new ProjectDetector(); const templateManager = new TemplateManager(); const pathResolver = new PathResolver(); - const configurationManager = new ConfigurationManager(); + const _configurationManager = new ConfigurationManager(); try { // Step 1: Detect project context using ProjectDetector @@ -28,11 +33,12 @@ export async function updateComponent(args: CommandArgs) { ErrorType.WORKSPACE_NOT_FOUND, 'No folder path provided for component update', { args }, - false + false, ); } - const projectContext = await projectDetector.detectProject(currentFolderPath); + const projectContext = + await projectDetector.detectProject(currentFolderPath); // Step 2: Extract component name from folder path const componentName = path.basename(currentFolderPath); @@ -41,7 +47,7 @@ export async function updateComponent(args: CommandArgs) { ErrorType.INVALID_COMPONENT_NAME, 'Cannot determine component name from folder path', { currentFolderPath }, - true + true, ); } @@ -51,23 +57,25 @@ export async function updateComponent(args: CommandArgs) { ErrorType.INVALID_COMPONENT_NAME, `Invalid component name: ${componentName}. Component names must be valid file/folder names.`, { componentName, folderPath: currentFolderPath }, - true + true, ); } // Step 4: Discover templates using TemplateManager with hierarchical discovery - const availableTemplates = await templateManager.discoverTemplates(projectContext.projectRoot); - + const availableTemplates = await templateManager.discoverTemplates( + projectContext.projectRoot, + ); + if (availableTemplates.length === 0) { throw new CodebotError( ErrorType.TEMPLATE_FOLDER_EMPTY, `No templates found in project. Searched in: ${projectContext.templatePath}`, - { + { projectRoot: projectContext.projectRoot, templatePath: projectContext.templatePath, - isMultiProject: projectContext.isMultiProject + isMultiProject: projectContext.isMultiProject, }, - true + true, ); } @@ -89,11 +97,13 @@ export async function updateComponent(args: CommandArgs) { ErrorType.TEMPLATE_NOT_FOUND, 'No template type selected', { availableTemplates: templateNames }, - true + true, ); } - selectedTemplate = availableTemplates.find(t => t.name === selectedTemplateName); + selectedTemplate = availableTemplates.find( + t => t.name === selectedTemplateName, + ); } if (!selectedTemplate) { @@ -101,34 +111,46 @@ export async function updateComponent(args: CommandArgs) { ErrorType.TEMPLATE_NOT_FOUND, 'Selected template not found', { availableTemplates }, - false + false, ); } // Step 6: Validate target path is within project boundaries const targetBasePath = currentFolderPath; - + // Ensure the target path is within project boundaries using PathResolver try { - pathResolver.resolveTargetPath(projectContext, componentName, path.dirname(currentFolderPath)); + pathResolver.resolveTargetPath( + projectContext, + componentName, + path.dirname(currentFolderPath), + ); } catch (pathError) { throw new CodebotError( ErrorType.FILE_SYSTEM_ERROR, `Target path validation failed: ${pathError instanceof Error ? pathError.message : 'Unknown error'}`, { targetPath: targetBasePath, projectContext }, - false + false, ); } - + // Step 7: Process templates hierarchically and update files (only create missing files) - const templateStructure = await templateManager.scanTemplateStructure(selectedTemplate.path); - - if (templateStructure.files.length === 0 && templateStructure.directories.length === 0) { + const templateStructure = await templateManager.scanTemplateStructure( + selectedTemplate.path, + ); + + if ( + templateStructure.files.length === 0 && + templateStructure.directories.length === 0 + ) { throw new CodebotError( ErrorType.TEMPLATE_FOLDER_EMPTY, `Template folder is empty: ${selectedTemplate.path}`, - { templatePath: selectedTemplate.path, templateName: selectedTemplate.name }, - false + { + templatePath: selectedTemplate.path, + templateName: selectedTemplate.name, + }, + false, ); } @@ -138,22 +160,29 @@ export async function updateComponent(args: CommandArgs) { } // Process the template hierarchy - const processedTemplate = await templateManager.processTemplateHierarchy(templateStructure, componentName); - + const processedTemplate = await templateManager.processTemplateHierarchy( + templateStructure, + componentName, + ); + // Track creation and skipping statistics const creationReport = { filesCreated: 0, filesSkipped: 0, directoriesCreated: 0, createdFiles: [] as string[], - skippedFiles: [] as string[] + skippedFiles: [] as string[], }; // Process root-level files await processFiles(processedTemplate.files, targetBasePath, creationReport); - + // Process directories recursively - await processDirectories(processedTemplate.directories, targetBasePath, creationReport); + await processDirectories( + processedTemplate.directories, + targetBasePath, + creationReport, + ); // Step 8: Show success message with details let successMessage = `Component '${componentName}' updated successfully!`; @@ -175,25 +204,34 @@ export async function updateComponent(args: CommandArgs) { } showMessage(successMessage, 'information'); - } catch (error) { // Handle multi-project specific errors if (error instanceof CodebotError) { let errorMessage = error.message; - + // Add context for multi-project scenarios - if (error.type === ErrorType.TEMPLATE_FOLDER_EMPTY && error.details?.isMultiProject) { - errorMessage += '\n\nTip: In multi-project workspaces, templates can be placed in:'; - errorMessage += `\n- Project-specific: ${error.details.projectRoot}/templates`; - errorMessage += `\n- Workspace-wide: ${error.details.workspaceRoot}/templates`; + const details = + typeof error.details === 'object' && error.details !== null + ? (error.details as Record) + : null; + if ( + error.type === ErrorType.TEMPLATE_FOLDER_EMPTY && + details?.isMultiProject + ) { + errorMessage += + '\n\nTip: In multi-project workspaces, templates can be placed in:'; + errorMessage += `\n- Project-specific: ${details.projectRoot}/templates`; + errorMessage += `\n- Workspace-wide: ${details.workspaceRoot}/templates`; } - + if (error.type === ErrorType.PROJECT_DETECTION_ERROR) { - errorMessage += '\n\nTip: Ensure you are running the command from within a valid project folder.'; + errorMessage += + '\n\nTip: Ensure you are running the command from within a valid project folder.'; } if (error.type === ErrorType.INVALID_COMPONENT_NAME) { - errorMessage += '\n\nTip: For update operations, make sure you are right-clicking on a component folder.'; + errorMessage += + '\n\nTip: For update operations, make sure you are right-clicking on a component folder.'; } showMessage(errorMessage, 'error'); @@ -203,9 +241,9 @@ export async function updateComponent(args: CommandArgs) { ErrorType.FILE_SYSTEM_ERROR, `Unexpected error during component update: ${error.message}`, { originalError: error.message, stack: error.stack }, - false + false, ); - + showMessage(unexpectedError.message, 'error'); } else { // Handle unknown errors @@ -216,15 +254,15 @@ export async function updateComponent(args: CommandArgs) { // Helper function to process files hierarchically async function processFiles( - files: ProcessedFile[], - basePath: string, + files: ProcessedFile[], + basePath: string, creationReport: { filesCreated: number; filesSkipped: number; directoriesCreated: number; createdFiles: string[]; skippedFiles: string[]; - } + }, ): Promise { for (const file of files) { const targetFilePath = path.join(basePath, file.targetPath); @@ -246,7 +284,7 @@ async function processFiles( ErrorType.TEMPLATE_PROCESSING_ERROR, `Failed to create file: ${file.targetPath}`, { targetPath: targetFilePath, error }, - false + false, ); } } @@ -254,15 +292,15 @@ async function processFiles( // Helper function to process directories hierarchically async function processDirectories( - directories: ProcessedDirectory[], - basePath: string, + directories: ProcessedDirectory[], + basePath: string, creationReport: { filesCreated: number; filesSkipped: number; directoriesCreated: number; createdFiles: string[]; skippedFiles: string[]; - } + }, ): Promise { for (const directory of directories) { const targetDirPath = path.join(basePath, directory.targetPath); @@ -275,8 +313,12 @@ async function processDirectories( // Process files in this directory await processFiles(directory.files, basePath, creationReport); - + // Process subdirectories recursively - await processDirectories(directory.subdirectories, basePath, creationReport); + await processDirectories( + directory.subdirectories, + basePath, + creationReport, + ); } } diff --git a/src/constants/index.ts b/src/constants/index.ts index 1b7a39a..fa29664 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1,22 +1,22 @@ -export const DEFAULT_TEMPLATE_FOLDER = 'templates'; -export const CONFIG_FILE_NAME = 'codebot.config.json'; -export const TEMPLATE_FILE_EXTENSION = '.hbs'; - -export const DEFAULT_CONFIGURATION = { - templateFolderPath: DEFAULT_TEMPLATE_FOLDER, - multiProject: { - enabled: true, - projectDetection: 'auto' as const - } -}; - -export const SUPPORTED_TEMPLATE_EXTENSIONS = ['.hbs']; -export const COMPONENT_NAME_REGEX = /^[a-zA-Z][a-zA-Z0-9_-]*$/; - -export const ERROR_MESSAGES = { - WORKSPACE_NOT_FOUND: 'Workspace path not found!', - INVALID_COMPONENT_NAME: 'Invalid component name!', - TEMPLATE_FOLDER_EMPTY: 'Templates folder is empty!', - TEMPLATE_NOT_FOUND: 'Template folder not found!', - INVALID_TEMPLATE_TYPE: 'Invalid template type!' -} as const; \ No newline at end of file +export const DEFAULT_TEMPLATE_FOLDER = 'templates'; +export const CONFIG_FILE_NAME = 'codebot.config.json'; +export const TEMPLATE_FILE_EXTENSION = '.hbs'; + +export const DEFAULT_CONFIGURATION = { + templateFolderPath: DEFAULT_TEMPLATE_FOLDER, + multiProject: { + enabled: true, + projectDetection: 'auto' as const, + }, +}; + +export const SUPPORTED_TEMPLATE_EXTENSIONS = ['.hbs']; +export const COMPONENT_NAME_REGEX = /^[a-zA-Z][a-zA-Z0-9_-]*$/; + +export const ERROR_MESSAGES = { + WORKSPACE_NOT_FOUND: 'Workspace path not found!', + INVALID_COMPONENT_NAME: 'Invalid component name!', + TEMPLATE_FOLDER_EMPTY: 'Templates folder is empty!', + TEMPLATE_NOT_FOUND: 'Template folder not found!', + INVALID_TEMPLATE_TYPE: 'Invalid template type!', +} as const; diff --git a/src/errors/CodebotError.ts b/src/errors/CodebotError.ts index 742361a..c78b0d7 100644 --- a/src/errors/CodebotError.ts +++ b/src/errors/CodebotError.ts @@ -1,114 +1,135 @@ -import { ErrorType } from '../types'; - -export class CodebotError extends Error { - public readonly type: ErrorType; - public readonly details?: any; - public readonly recoverable: boolean; - - constructor(type: ErrorType, message: string, details?: any, recoverable: boolean = false) { - super(message); - this.name = 'CodebotError'; - this.type = type; - this.details = details; - this.recoverable = recoverable; - } - - static workspaceNotFound(): CodebotError { - return new CodebotError( - ErrorType.WORKSPACE_NOT_FOUND, - 'Workspace path not found!', - undefined, - false - ); - } - - static invalidComponentName(name?: string): CodebotError { - return new CodebotError( - ErrorType.INVALID_COMPONENT_NAME, - 'Invalid component name!', - { providedName: name }, - true - ); - } - - static templateFolderEmpty(path: string): CodebotError { - return new CodebotError( - ErrorType.TEMPLATE_FOLDER_EMPTY, - 'Templates folder is empty!', - { templatePath: path }, - true - ); - } - - static templateNotFound(templateType: string): CodebotError { - return new CodebotError( - ErrorType.TEMPLATE_NOT_FOUND, - 'Template folder not found!', - { templateType }, - true - ); - } - - static fileSystemError(operation: string, path: string, originalError?: Error): CodebotError { - return new CodebotError( - ErrorType.FILE_SYSTEM_ERROR, - `File system error during ${operation}`, - { path, originalError: originalError?.message }, - false - ); - } - - static templateProcessingError(templatePath: string, originalError?: Error): CodebotError { - return new CodebotError( - ErrorType.TEMPLATE_PROCESSING_ERROR, - 'Error processing template', - { templatePath, originalError: originalError?.message }, - true - ); - } - - static projectDetectionError(folderPath: string, originalError?: Error): CodebotError { - return new CodebotError( - ErrorType.PROJECT_DETECTION_ERROR, - 'Error detecting project context', - { folderPath, originalError: originalError?.message }, - true - ); - } - - static configurationError(configPath: string, originalError?: Error): CodebotError { - return new CodebotError( - ErrorType.CONFIGURATION_ERROR, - 'Error loading configuration', - { configPath, originalError: originalError?.message }, - true - ); - } - - static pathValidationError(path: string, reason: string): CodebotError { - return new CodebotError( - ErrorType.FILE_SYSTEM_ERROR, - `Invalid path: ${reason}`, - { path, reason }, - true - ); - } - - static pathTraversalError(path: string): CodebotError { - return new CodebotError( - ErrorType.FILE_SYSTEM_ERROR, - 'Path traversal attempt detected', - { path }, - false - ); - } - - static pathOutsideBoundariesError(path: string, projectRoot: string): CodebotError { - return new CodebotError( - ErrorType.FILE_SYSTEM_ERROR, - 'Path is outside project boundaries', - { path, projectRoot }, - false - ); - } -} \ No newline at end of file +import { ErrorType } from '../types'; + +export class CodebotError extends Error { + public readonly type: ErrorType; + public readonly details?: unknown; + public readonly recoverable: boolean; + + constructor( + type: ErrorType, + message: string, + details?: unknown, + recoverable = false, + ) { + super(message); + this.name = 'CodebotError'; + this.type = type; + this.details = details; + this.recoverable = recoverable; + } + + static workspaceNotFound(): CodebotError { + return new CodebotError( + ErrorType.WORKSPACE_NOT_FOUND, + 'Workspace path not found!', + undefined, + false, + ); + } + + static invalidComponentName(name?: string): CodebotError { + return new CodebotError( + ErrorType.INVALID_COMPONENT_NAME, + 'Invalid component name!', + { providedName: name }, + true, + ); + } + + static templateFolderEmpty(path: string): CodebotError { + return new CodebotError( + ErrorType.TEMPLATE_FOLDER_EMPTY, + 'Templates folder is empty!', + { templatePath: path }, + true, + ); + } + + static templateNotFound(templateType: string): CodebotError { + return new CodebotError( + ErrorType.TEMPLATE_NOT_FOUND, + 'Template folder not found!', + { templateType }, + true, + ); + } + + static fileSystemError( + operation: string, + path: string, + originalError?: Error, + ): CodebotError { + return new CodebotError( + ErrorType.FILE_SYSTEM_ERROR, + `File system error during ${operation}`, + { path, originalError: originalError?.message }, + false, + ); + } + + static templateProcessingError( + templatePath: string, + originalError?: Error, + ): CodebotError { + return new CodebotError( + ErrorType.TEMPLATE_PROCESSING_ERROR, + 'Error processing template', + { templatePath, originalError: originalError?.message }, + true, + ); + } + + static projectDetectionError( + folderPath: string, + originalError?: Error, + ): CodebotError { + return new CodebotError( + ErrorType.PROJECT_DETECTION_ERROR, + 'Error detecting project context', + { folderPath, originalError: originalError?.message }, + true, + ); + } + + static configurationError( + configPath: string, + originalError?: Error, + ): CodebotError { + return new CodebotError( + ErrorType.CONFIGURATION_ERROR, + 'Error loading configuration', + { configPath, originalError: originalError?.message }, + true, + ); + } + + static pathValidationError(path: string, reason: string): CodebotError { + return new CodebotError( + ErrorType.FILE_SYSTEM_ERROR, + `Invalid path: ${reason}`, + { path, reason }, + true, + ); + } + + static pathTraversalError(path: string): CodebotError { + return new CodebotError( + ErrorType.FILE_SYSTEM_ERROR, + 'Path traversal attempt detected', + { path }, + false, + ); + } + + static pathOutsideBoundariesError( + path: string, + projectRoot: string, + ): CodebotError { + return new CodebotError( + ErrorType.FILE_SYSTEM_ERROR, + 'Path is outside project boundaries', + { path, projectRoot }, + false, + ); + } +} diff --git a/src/errors/index.ts b/src/errors/index.ts index c011846..72c12f8 100644 --- a/src/errors/index.ts +++ b/src/errors/index.ts @@ -1 +1 @@ -export * from './CodebotError'; \ No newline at end of file +export * from './CodebotError'; diff --git a/src/helpers/checkExistsFile.ts b/src/helpers/checkExistsFile.ts index 5c3b85e..a47dcd0 100644 --- a/src/helpers/checkExistsFile.ts +++ b/src/helpers/checkExistsFile.ts @@ -4,7 +4,7 @@ export function checkExistsFile(path: string) { try { readFile(path); return true; - } catch (error) { + } catch (_error) { return false; } } diff --git a/src/helpers/checkExistsFolder.ts b/src/helpers/checkExistsFolder.ts index 006d1ee..705ee8c 100644 --- a/src/helpers/checkExistsFolder.ts +++ b/src/helpers/checkExistsFolder.ts @@ -1,4 +1,4 @@ -import * as fs from 'fs'; +import * as fs from 'node:fs'; export function checkExistsFolder(path: string) { return fs.existsSync(path); diff --git a/src/helpers/createFile.ts b/src/helpers/createFile.ts index 9436d46..4c69b43 100644 --- a/src/helpers/createFile.ts +++ b/src/helpers/createFile.ts @@ -1,4 +1,4 @@ -import * as fs from 'fs'; +import * as fs from 'node:fs'; export function createFile(path: string, data: string) { fs.writeFileSync(path, data, { encoding: 'utf-8' }); diff --git a/src/helpers/createFolder.ts b/src/helpers/createFolder.ts index fc3b5d8..5192cf3 100644 --- a/src/helpers/createFolder.ts +++ b/src/helpers/createFolder.ts @@ -1,4 +1,4 @@ -import * as fs from 'fs'; +import * as fs from 'node:fs'; export function createFolder(path: string) { fs.mkdirSync(path, { recursive: true }); diff --git a/src/helpers/getConfigurationFile.ts b/src/helpers/getConfigurationFile.ts index 05af581..1099cb7 100644 --- a/src/helpers/getConfigurationFile.ts +++ b/src/helpers/getConfigurationFile.ts @@ -1,24 +1,24 @@ -import * as path from 'path'; -import { readFile } from './readFile'; -import { ConfigurationFile } from '../types'; +import * as path from 'node:path'; +import type { ConfigurationFile } from '../types'; import { getWorkspaceFolder } from './getWorkspaceFolder'; +import { readFile } from './readFile'; -export function getConfigurationFile(projectPath?: string): ConfigurationFile | null { +export function getConfigurationFile( + projectPath?: string, +): ConfigurationFile | null { try { const workspaceFolderPath = getWorkspaceFolder(); const targetPath = projectPath || workspaceFolderPath; if (targetPath) { - const file = readFile( - path.resolve(targetPath, 'codebot.config.json'), - ); + const file = readFile(path.resolve(targetPath, 'codebot.config.json')); const configurationFile = JSON.parse(file); return configurationFile; } return null; - } catch (error) { + } catch (_error) { return null; } } diff --git a/src/helpers/getFilesByFolder.ts b/src/helpers/getFilesByFolder.ts index 36ed0f3..e3f2fd5 100644 --- a/src/helpers/getFilesByFolder.ts +++ b/src/helpers/getFilesByFolder.ts @@ -1,4 +1,4 @@ -import * as fs from 'fs'; +import * as fs from 'node:fs'; export function getFilesByFolder(path: string) { return fs.readdirSync(path); diff --git a/src/helpers/getWorkspaceFolder.ts b/src/helpers/getWorkspaceFolder.ts index 19e7e3c..42f3b06 100644 --- a/src/helpers/getWorkspaceFolder.ts +++ b/src/helpers/getWorkspaceFolder.ts @@ -1,8 +1,5 @@ import * as vscode from 'vscode'; export function getWorkspaceFolder() { - return ( - vscode?.workspace?.workspaceFolders && - vscode?.workspace?.workspaceFolders[0]?.uri?.fsPath - ); + return vscode?.workspace?.workspaceFolders?.[0]?.uri?.fsPath; } diff --git a/src/helpers/index.ts b/src/helpers/index.ts index 0968884..13d9b13 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -1,15 +1,15 @@ +export * from './buildTemplate'; +export * from './checkExistsFile'; +export * from './checkExistsFolder'; +export * from './createFile'; +export * from './createFolder'; +export * from './filterTemplatesFiles'; +export * from './formatTemplateName'; export * from './formatToPascalCase'; -export * from './getTextByInputBox'; -export * from './getWorkspaceFolder'; export * from './getConfigurationFile'; -export * from './checkExistsFolder'; export * from './getFilesByFolder'; +export * from './getTextByInputBox'; +export * from './getWorkspaceFolder'; export * from './readFile'; -export * from './buildTemplate'; -export * from './formatTemplateName'; -export * from './createFolder'; -export * from './createFile'; export * from './showMessage'; -export * from './filterTemplatesFiles'; -export * from './checkExistsFile'; export * from './showSearchDropdown'; diff --git a/src/helpers/readFile.ts b/src/helpers/readFile.ts index 756ddef..10cddee 100644 --- a/src/helpers/readFile.ts +++ b/src/helpers/readFile.ts @@ -1,4 +1,4 @@ -import * as fs from 'fs'; +import * as fs from 'node:fs'; export function readFile(path: string) { return fs.readFileSync(path, 'utf8'); diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 7d6d984..6c52384 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -1,54 +1,72 @@ -import { ProjectContext, TemplateType, ConfigurationFile, CodebotError, FileSystemEntry, TemplateStructure, ProcessedTemplate } from '../types'; - -export interface IProjectDetector { - detectProject(folderPath: string): Promise; - isMultiProjectWorkspace(): boolean; - resolveProjectRoot(folderPath: string): string; -} - -export interface IConfigurationManager { - getConfiguration(projectPath: string): Promise; - resolveTemplatePath(projectPath: string): string; - getConfigurationPath(projectPath: string): string; -} - -export interface ITemplateManager { - discoverTemplates(projectPath: string): Promise; - processTemplate(templatePath: string, componentName: string): Promise; - getTemplateFiles(templateType: string): string[]; - scanTemplateStructure(templatePath: string): Promise; - processTemplateHierarchy(templateStructure: TemplateStructure, componentName: string): Promise; - invalidateCache(): void; -} - -export interface IFileSystemManager { - createFile(path: string, content: string): Promise; - createFolder(path: string): Promise; - createFolderRecursive(path: string): Promise; - fileExists(path: string): Promise; - folderExists(path: string): Promise; - readFile(path: string): Promise; - listFiles(path: string): Promise; - listDirectories(path: string): Promise; - listFilesRecursive(path: string): Promise; - copyDirectoryStructure(sourcePath: string, targetPath: string): Promise; -} - -export interface IPathResolver { - resolveTemplatePath(projectContext: ProjectContext, templateName: string): string; - resolveTargetPath(projectContext: ProjectContext, componentName: string, selectedFolderPath?: string): string; - validatePath(path: string): boolean; - sanitizePath(path: string): string; -} - -export interface IErrorHandler { - handleError(error: CodebotError): void; - createError(type: string, message: string, details?: any): CodebotError; - isRecoverable(error: CodebotError): boolean; -} - -export interface IUIManager { - getComponentName(): Promise; - selectTemplate(templates: TemplateType[]): Promise; - showMessage(message: string, type: 'info' | 'error' | 'warning'): void; -} \ No newline at end of file +import type { + CodebotError, + ConfigurationFile, + FileSystemEntry, + ProcessedTemplate, + ProjectContext, + TemplateStructure, + TemplateType, +} from '../types'; + +export interface IProjectDetector { + detectProject(folderPath: string): Promise; + isMultiProjectWorkspace(): boolean; + resolveProjectRoot(folderPath: string): string; +} + +export interface IConfigurationManager { + getConfiguration(projectPath: string): Promise; + resolveTemplatePath(projectPath: string): string; + getConfigurationPath(projectPath: string): string; +} + +export interface ITemplateManager { + discoverTemplates(projectPath: string): Promise; + processTemplate(templatePath: string, componentName: string): Promise; + getTemplateFiles(templateType: string): string[]; + scanTemplateStructure(templatePath: string): Promise; + processTemplateHierarchy( + templateStructure: TemplateStructure, + componentName: string, + ): Promise; + invalidateCache(): void; +} + +export interface IFileSystemManager { + createFile(path: string, content: string): Promise; + createFolder(path: string): Promise; + createFolderRecursive(path: string): Promise; + fileExists(path: string): Promise; + folderExists(path: string): Promise; + readFile(path: string): Promise; + listFiles(path: string): Promise; + listDirectories(path: string): Promise; + listFilesRecursive(path: string): Promise; + copyDirectoryStructure(sourcePath: string, targetPath: string): Promise; +} + +export interface IPathResolver { + resolveTemplatePath( + projectContext: ProjectContext, + templateName: string, + ): string; + resolveTargetPath( + projectContext: ProjectContext, + componentName: string, + selectedFolderPath?: string, + ): string; + validatePath(path: string): boolean; + sanitizePath(path: string): string; +} + +export interface IErrorHandler { + handleError(error: CodebotError): void; + createError(type: string, message: string, details?: unknown): CodebotError; + isRecoverable(error: CodebotError): boolean; +} + +export interface IUIManager { + getComponentName(): Promise; + selectTemplate(templates: TemplateType[]): Promise; + showMessage(message: string, type: 'info' | 'error' | 'warning'): void; +} diff --git a/src/managers/ConfigurationManager.ts b/src/managers/ConfigurationManager.ts index 2eb824c..fab8126 100644 --- a/src/managers/ConfigurationManager.ts +++ b/src/managers/ConfigurationManager.ts @@ -1,198 +1,218 @@ -import * as path from 'path'; -import { IConfigurationManager, ConfigurationFile } from '../types'; -import { CodebotError } from '../errors'; -import { DEFAULT_CONFIGURATION, CONFIG_FILE_NAME, DEFAULT_TEMPLATE_FOLDER } from '../constants'; - -export class ConfigurationManager implements IConfigurationManager { - private configCache = new Map(); - - async getConfiguration(projectPath: string): Promise { - try { - // Check cache first - const cacheKey = this.normalizePath(projectPath); - if (this.configCache.has(cacheKey)) { - return this.configCache.get(cacheKey)!; - } - - // Try hierarchical configuration resolution - const config = await this.resolveHierarchicalConfiguration(projectPath); - - // Cache the result - this.configCache.set(cacheKey, config); - return config; - } catch (error) { - throw CodebotError.configurationError(projectPath, error as Error); - } - } - - resolveTemplatePath(projectPath: string): string { - const configPath = this.getConfigurationPath(projectPath); - const config = this.loadConfigurationSync(configPath); - - const templateFolder = config?.templateFolderPath || DEFAULT_TEMPLATE_FOLDER; - return path.resolve(projectPath, templateFolder); - } - - getConfigurationPath(projectPath: string): string { - return path.join(projectPath, CONFIG_FILE_NAME); - } - - private async resolveHierarchicalConfiguration(projectPath: string): Promise { - const configurations: Partial[] = []; - - // 1. Try project-specific configuration - const projectConfig = await this.loadConfiguration(projectPath); - if (projectConfig) { - configurations.push(projectConfig); - } - - // 2. Try workspace root configuration (if different from project) - const workspaceRoot = this.findWorkspaceRoot(projectPath); - if (workspaceRoot && workspaceRoot !== projectPath) { - const workspaceConfig = await this.loadConfiguration(workspaceRoot); - if (workspaceConfig) { - configurations.push(workspaceConfig); - } - } - - // 3. Use default configuration as fallback - configurations.push(DEFAULT_CONFIGURATION); - - // Merge configurations (project takes precedence over workspace, workspace over default) - return this.mergeConfigurations(configurations); - } - - private async loadConfiguration(configPath: string): Promise { - try { - const fs = require('fs').promises; - const fullPath = path.join(configPath, CONFIG_FILE_NAME); - - const configContent = await fs.readFile(fullPath, 'utf8'); - return JSON.parse(configContent) as ConfigurationFile; - } catch (error) { - // Configuration file doesn't exist or is invalid - return null; - } - } - - private loadConfigurationSync(configPath: string): ConfigurationFile | null { - try { - const fs = require('fs'); - const fullPath = path.join(configPath, CONFIG_FILE_NAME); - - const configContent = fs.readFileSync(fullPath, 'utf8'); - return JSON.parse(configContent) as ConfigurationFile; - } catch (error) { - return null; - } - } - - private mergeConfigurations(configurations: Partial[]): ConfigurationFile { - const merged: ConfigurationFile = { - templateFolderPath: DEFAULT_TEMPLATE_FOLDER, - multiProject: { - enabled: true, - projectDetection: 'auto' - } - }; - - // Merge from least specific to most specific (reverse order) - for (const config of configurations.reverse()) { - if (config.templateFolderPath !== undefined) { - merged.templateFolderPath = config.templateFolderPath; - } - - if (config.multiProject) { - merged.multiProject = { - ...merged.multiProject, - ...config.multiProject - }; - } - } - - return merged; - } - - private findWorkspaceRoot(projectPath: string): string | null { - // This is a simplified implementation - // In a real scenario, you'd use VS Code's workspace API - const vscode = require('vscode'); - const workspaceFolders = vscode.workspace.workspaceFolders; - - if (!workspaceFolders || workspaceFolders.length === 0) { - return null; - } - - // Find the workspace folder that contains the project path - for (const folder of workspaceFolders) { - const workspaceRoot = folder.uri.fsPath; - if (projectPath.startsWith(workspaceRoot)) { - return workspaceRoot; - } - } - - return workspaceFolders[0].uri.fsPath; - } - - private normalizePath(filePath: string): string { - return path.resolve(filePath).replace(/\\/g, '/'); - } - - // Method to clear cache when configurations change - clearCache(): void { - this.configCache.clear(); - } - - // Method to invalidate specific configuration - invalidateConfiguration(projectPath: string): void { - const cacheKey = this.normalizePath(projectPath); - this.configCache.delete(cacheKey); - } - - // Method to validate configuration - validateConfiguration(config: ConfigurationFile): boolean { - try { - // Basic validation - if (config.templateFolderPath && typeof config.templateFolderPath !== 'string') { - return false; - } - - if (config.multiProject) { - if (typeof config.multiProject.enabled !== 'boolean') { - return false; - } - - if (!['auto', 'manual'].includes(config.multiProject.projectDetection)) { - return false; - } - } - - return true; - } catch (error) { - return false; - } - } - - // Method to create default configuration file - async createDefaultConfiguration(projectPath: string): Promise { - try { - const fs = require('fs').promises; - const configPath = this.getConfigurationPath(projectPath); - - const defaultConfig = { - templateFolderPath: DEFAULT_TEMPLATE_FOLDER, - multiProject: { - enabled: true, - projectDetection: 'auto' - } - }; - - await fs.writeFile(configPath, JSON.stringify(defaultConfig, null, 2), 'utf8'); - - // Invalidate cache for this path - this.invalidateConfiguration(projectPath); - } catch (error) { - throw CodebotError.configurationError(projectPath, error as Error); - } - } -} \ No newline at end of file +import * as path from 'node:path'; +import { + CONFIG_FILE_NAME, + DEFAULT_CONFIGURATION, + DEFAULT_TEMPLATE_FOLDER, +} from '../constants'; +import { CodebotError } from '../errors'; +import type { ConfigurationFile, IConfigurationManager } from '../types'; + +export class ConfigurationManager implements IConfigurationManager { + private configCache = new Map(); + + async getConfiguration(projectPath: string): Promise { + try { + // Check cache first + const cacheKey = this.normalizePath(projectPath); + if (this.configCache.has(cacheKey)) { + return this.configCache.get(cacheKey) as ConfigurationFile; + } + + // Try hierarchical configuration resolution + const config = await this.resolveHierarchicalConfiguration(projectPath); + + // Cache the result + this.configCache.set(cacheKey, config); + return config; + } catch (error) { + throw CodebotError.configurationError(projectPath, error as Error); + } + } + + resolveTemplatePath(projectPath: string): string { + const configPath = this.getConfigurationPath(projectPath); + const config = this.loadConfigurationSync(configPath); + + const templateFolder = + config?.templateFolderPath || DEFAULT_TEMPLATE_FOLDER; + return path.resolve(projectPath, templateFolder); + } + + getConfigurationPath(projectPath: string): string { + return path.join(projectPath, CONFIG_FILE_NAME); + } + + private async resolveHierarchicalConfiguration( + projectPath: string, + ): Promise { + const configurations: Partial[] = []; + + // 1. Try project-specific configuration + const projectConfig = await this.loadConfiguration(projectPath); + if (projectConfig) { + configurations.push(projectConfig); + } + + // 2. Try workspace root configuration (if different from project) + const workspaceRoot = this.findWorkspaceRoot(projectPath); + if (workspaceRoot && workspaceRoot !== projectPath) { + const workspaceConfig = await this.loadConfiguration(workspaceRoot); + if (workspaceConfig) { + configurations.push(workspaceConfig); + } + } + + // 3. Use default configuration as fallback + configurations.push(DEFAULT_CONFIGURATION); + + // Merge configurations (project takes precedence over workspace, workspace over default) + return this.mergeConfigurations(configurations); + } + + private async loadConfiguration( + configPath: string, + ): Promise { + try { + const fs = require('node:fs').promises; + const fullPath = path.join(configPath, CONFIG_FILE_NAME); + + const configContent = await fs.readFile(fullPath, 'utf8'); + return JSON.parse(configContent) as ConfigurationFile; + } catch (_error) { + // Configuration file doesn't exist or is invalid + return null; + } + } + + private loadConfigurationSync(configPath: string): ConfigurationFile | null { + try { + const fs = require('node:fs'); + const fullPath = path.join(configPath, CONFIG_FILE_NAME); + + const configContent = fs.readFileSync(fullPath, 'utf8'); + return JSON.parse(configContent) as ConfigurationFile; + } catch (_error) { + return null; + } + } + + private mergeConfigurations( + configurations: Partial[], + ): ConfigurationFile { + const merged: ConfigurationFile = { + templateFolderPath: DEFAULT_TEMPLATE_FOLDER, + multiProject: { + enabled: true, + projectDetection: 'auto', + }, + }; + + // Merge from least specific to most specific (reverse order) + for (const config of configurations.reverse()) { + if (config.templateFolderPath !== undefined) { + merged.templateFolderPath = config.templateFolderPath; + } + + if (config.multiProject) { + merged.multiProject = { + ...merged.multiProject, + ...config.multiProject, + }; + } + } + + return merged; + } + + private findWorkspaceRoot(projectPath: string): string | null { + // This is a simplified implementation + // In a real scenario, you'd use VS Code's workspace API + const vscode = require('vscode'); + const workspaceFolders = vscode.workspace.workspaceFolders; + + if (!workspaceFolders || workspaceFolders.length === 0) { + return null; + } + + // Find the workspace folder that contains the project path + for (const folder of workspaceFolders) { + const workspaceRoot = folder.uri.fsPath; + if (projectPath.startsWith(workspaceRoot)) { + return workspaceRoot; + } + } + + return workspaceFolders[0].uri.fsPath; + } + + private normalizePath(filePath: string): string { + return path.resolve(filePath).replace(/\\/g, '/'); + } + + // Method to clear cache when configurations change + clearCache(): void { + this.configCache.clear(); + } + + // Method to invalidate specific configuration + invalidateConfiguration(projectPath: string): void { + const cacheKey = this.normalizePath(projectPath); + this.configCache.delete(cacheKey); + } + + // Method to validate configuration + validateConfiguration(config: ConfigurationFile): boolean { + try { + // Basic validation + if ( + config.templateFolderPath && + typeof config.templateFolderPath !== 'string' + ) { + return false; + } + + if (config.multiProject) { + if (typeof config.multiProject.enabled !== 'boolean') { + return false; + } + + if ( + !['auto', 'manual'].includes(config.multiProject.projectDetection) + ) { + return false; + } + } + + return true; + } catch (_error) { + return false; + } + } + + // Method to create default configuration file + async createDefaultConfiguration(projectPath: string): Promise { + try { + const fs = require('node:fs').promises; + const configPath = this.getConfigurationPath(projectPath); + + const defaultConfig = { + templateFolderPath: DEFAULT_TEMPLATE_FOLDER, + multiProject: { + enabled: true, + projectDetection: 'auto', + }, + }; + + await fs.writeFile( + configPath, + JSON.stringify(defaultConfig, null, 2), + 'utf8', + ); + + // Invalidate cache for this path + this.invalidateConfiguration(projectPath); + } catch (error) { + throw CodebotError.configurationError(projectPath, error as Error); + } + } +} diff --git a/src/managers/FileSystemManager.ts b/src/managers/FileSystemManager.ts index 0bb4c8e..01a96a2 100644 --- a/src/managers/FileSystemManager.ts +++ b/src/managers/FileSystemManager.ts @@ -1,205 +1,265 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import { IFileSystemManager } from '../interfaces'; -import { FileSystemEntry, ErrorType, CodebotError } from '../types'; - -export class FileSystemManager implements IFileSystemManager { - private static readonly MAX_DEPTH = 15; // Security limit for directory depth - - async createFile(filePath: string, content: string): Promise { - try { - // Ensure directory exists before creating file - const dir = path.dirname(filePath); - await this.createFolderRecursive(dir); - - fs.writeFileSync(filePath, content, { encoding: 'utf-8' }); - } catch (error) { - throw this.createFileSystemError(`Failed to create file: ${filePath}`, error); - } - } - - async createFolder(folderPath: string): Promise { - try { - if (!fs.existsSync(folderPath)) { - fs.mkdirSync(folderPath); - } - } catch (error) { - throw this.createFileSystemError(`Failed to create folder: ${folderPath}`, error); - } - } - - async createFolderRecursive(folderPath: string): Promise { - try { - if (!fs.existsSync(folderPath)) { - fs.mkdirSync(folderPath, { recursive: true }); - } - } catch (error) { - throw this.createFileSystemError(`Failed to create folder recursively: ${folderPath}`, error); - } - } - - async fileExists(filePath: string): Promise { - try { - const stats = fs.statSync(filePath); - return stats.isFile(); - } catch { - return false; - } - } - - async folderExists(folderPath: string): Promise { - try { - const stats = fs.statSync(folderPath); - return stats.isDirectory(); - } catch { - return false; - } - } - - async readFile(filePath: string): Promise { - try { - return fs.readFileSync(filePath, 'utf8'); - } catch (error) { - throw this.createFileSystemError(`Failed to read file: ${filePath}`, error); - } - } - - async listFiles(folderPath: string): Promise { - try { - if (!await this.folderExists(folderPath)) { - return []; - } - - const entries = fs.readdirSync(folderPath); - const files: string[] = []; - - for (const entry of entries) { - const fullPath = path.join(folderPath, entry); - const stats = fs.statSync(fullPath); - if (stats.isFile()) { - files.push(entry); - } - } - - return files; - } catch (error) { - throw this.createFileSystemError(`Failed to list files in: ${folderPath}`, error); - } - } - - async listDirectories(folderPath: string): Promise { - try { - if (!await this.folderExists(folderPath)) { - return []; - } - - const entries = fs.readdirSync(folderPath); - const directories: string[] = []; - - for (const entry of entries) { - const fullPath = path.join(folderPath, entry); - const stats = fs.statSync(fullPath); - if (stats.isDirectory()) { - directories.push(entry); - } - } - - return directories; - } catch (error) { - throw this.createFileSystemError(`Failed to list directories in: ${folderPath}`, error); - } - } - - async listFilesRecursive(folderPath: string, currentDepth: number = 0): Promise { - if (currentDepth > FileSystemManager.MAX_DEPTH) { - throw this.createFileSystemError(`Maximum directory depth exceeded: ${FileSystemManager.MAX_DEPTH}`, null); - } - - try { - if (!await this.folderExists(folderPath)) { - return []; - } - - const entries: FileSystemEntry[] = []; - const items = fs.readdirSync(folderPath); - - for (const item of items) { - const fullPath = path.join(folderPath, item); - const stats = fs.statSync(fullPath); - const relativePath = path.relative(folderPath, fullPath); - - if (stats.isDirectory()) { - const children = await this.listFilesRecursive(fullPath, currentDepth + 1); - entries.push({ - name: item, - path: fullPath, - relativePath, - isDirectory: true, - children - }); - } else { - entries.push({ - name: item, - path: fullPath, - relativePath, - isDirectory: false - }); - } - } - - return entries; - } catch (error) { - // Re-throw CodebotError instances (including depth errors) without wrapping - if (error && typeof error === 'object' && 'type' in error && error.type === ErrorType.FILE_SYSTEM_ERROR) { - throw error; - } - throw this.createFileSystemError(`Failed to list files recursively in: ${folderPath}`, error); - } - } - - async copyDirectoryStructure(sourcePath: string, targetPath: string): Promise { - try { - if (!await this.folderExists(sourcePath)) { - throw this.createFileSystemError(`Source directory does not exist: ${sourcePath}`, null); - } - - await this.createFolderRecursive(targetPath); - - const entries = await this.listFilesRecursive(sourcePath); - await this.copyEntriesRecursive(entries, sourcePath, targetPath); - } catch (error) { - // Re-throw CodebotError instances without wrapping - if (error && typeof error === 'object' && 'type' in error && error.type === ErrorType.FILE_SYSTEM_ERROR) { - throw error; - } - throw this.createFileSystemError(`Failed to copy directory structure from ${sourcePath} to ${targetPath}`, error); - } - } - - private async copyEntriesRecursive(entries: FileSystemEntry[], sourcePath: string, targetPath: string): Promise { - for (const entry of entries) { - const sourceEntryPath = entry.path; - const relativeToSource = path.relative(sourcePath, sourceEntryPath); - const targetEntryPath = path.join(targetPath, relativeToSource); - - if (entry.isDirectory) { - await this.createFolderRecursive(targetEntryPath); - if (entry.children) { - await this.copyEntriesRecursive(entry.children, sourcePath, targetPath); - } - } else { - const content = await this.readFile(sourceEntryPath); - await this.createFile(targetEntryPath, content); - } - } - } - - private createFileSystemError(message: string, originalError: any): CodebotError { - return { - type: ErrorType.FILE_SYSTEM_ERROR, - message, - details: originalError, - recoverable: false - }; - } -} \ No newline at end of file +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import type { IFileSystemManager } from '../interfaces'; +import { type CodebotError, ErrorType, type FileSystemEntry } from '../types'; + +export class FileSystemManager implements IFileSystemManager { + private static readonly MAX_DEPTH = 15; // Security limit for directory depth + + async createFile(filePath: string, content: string): Promise { + try { + // Ensure directory exists before creating file + const dir = path.dirname(filePath); + await this.createFolderRecursive(dir); + + fs.writeFileSync(filePath, content, { encoding: 'utf-8' }); + } catch (error) { + throw this.createFileSystemError( + `Failed to create file: ${filePath}`, + error, + ); + } + } + + async createFolder(folderPath: string): Promise { + try { + if (!fs.existsSync(folderPath)) { + fs.mkdirSync(folderPath); + } + } catch (error) { + throw this.createFileSystemError( + `Failed to create folder: ${folderPath}`, + error, + ); + } + } + + async createFolderRecursive(folderPath: string): Promise { + try { + if (!fs.existsSync(folderPath)) { + fs.mkdirSync(folderPath, { recursive: true }); + } + } catch (error) { + throw this.createFileSystemError( + `Failed to create folder recursively: ${folderPath}`, + error, + ); + } + } + + async fileExists(filePath: string): Promise { + try { + const stats = fs.statSync(filePath); + return stats.isFile(); + } catch { + return false; + } + } + + async folderExists(folderPath: string): Promise { + try { + const stats = fs.statSync(folderPath); + return stats.isDirectory(); + } catch { + return false; + } + } + + async readFile(filePath: string): Promise { + try { + return fs.readFileSync(filePath, 'utf8'); + } catch (error) { + throw this.createFileSystemError( + `Failed to read file: ${filePath}`, + error, + ); + } + } + + async listFiles(folderPath: string): Promise { + try { + if (!(await this.folderExists(folderPath))) { + return []; + } + + const entries = fs.readdirSync(folderPath); + const files: string[] = []; + + for (const entry of entries) { + const fullPath = path.join(folderPath, entry); + const stats = fs.statSync(fullPath); + if (stats.isFile()) { + files.push(entry); + } + } + + return files; + } catch (error) { + throw this.createFileSystemError( + `Failed to list files in: ${folderPath}`, + error, + ); + } + } + + async listDirectories(folderPath: string): Promise { + try { + if (!(await this.folderExists(folderPath))) { + return []; + } + + const entries = fs.readdirSync(folderPath); + const directories: string[] = []; + + for (const entry of entries) { + const fullPath = path.join(folderPath, entry); + const stats = fs.statSync(fullPath); + if (stats.isDirectory()) { + directories.push(entry); + } + } + + return directories; + } catch (error) { + throw this.createFileSystemError( + `Failed to list directories in: ${folderPath}`, + error, + ); + } + } + + async listFilesRecursive( + folderPath: string, + currentDepth = 0, + ): Promise { + if (currentDepth > FileSystemManager.MAX_DEPTH) { + throw this.createFileSystemError( + `Maximum directory depth exceeded: ${FileSystemManager.MAX_DEPTH}`, + null, + ); + } + + try { + if (!(await this.folderExists(folderPath))) { + return []; + } + + const entries: FileSystemEntry[] = []; + const items = fs.readdirSync(folderPath); + + for (const item of items) { + const fullPath = path.join(folderPath, item); + const stats = fs.statSync(fullPath); + const relativePath = path.relative(folderPath, fullPath); + + if (stats.isDirectory()) { + const children = await this.listFilesRecursive( + fullPath, + currentDepth + 1, + ); + entries.push({ + name: item, + path: fullPath, + relativePath, + isDirectory: true, + children, + }); + } else { + entries.push({ + name: item, + path: fullPath, + relativePath, + isDirectory: false, + }); + } + } + + return entries; + } catch (error) { + // Re-throw CodebotError instances (including depth errors) without wrapping + if ( + error && + typeof error === 'object' && + 'type' in error && + error.type === ErrorType.FILE_SYSTEM_ERROR + ) { + throw error; + } + throw this.createFileSystemError( + `Failed to list files recursively in: ${folderPath}`, + error, + ); + } + } + + async copyDirectoryStructure( + sourcePath: string, + targetPath: string, + ): Promise { + try { + if (!(await this.folderExists(sourcePath))) { + throw this.createFileSystemError( + `Source directory does not exist: ${sourcePath}`, + null, + ); + } + + await this.createFolderRecursive(targetPath); + + const entries = await this.listFilesRecursive(sourcePath); + await this.copyEntriesRecursive(entries, sourcePath, targetPath); + } catch (error) { + // Re-throw CodebotError instances without wrapping + if ( + error && + typeof error === 'object' && + 'type' in error && + error.type === ErrorType.FILE_SYSTEM_ERROR + ) { + throw error; + } + throw this.createFileSystemError( + `Failed to copy directory structure from ${sourcePath} to ${targetPath}`, + error, + ); + } + } + + private async copyEntriesRecursive( + entries: FileSystemEntry[], + sourcePath: string, + targetPath: string, + ): Promise { + for (const entry of entries) { + const sourceEntryPath = entry.path; + const relativeToSource = path.relative(sourcePath, sourceEntryPath); + const targetEntryPath = path.join(targetPath, relativeToSource); + + if (entry.isDirectory) { + await this.createFolderRecursive(targetEntryPath); + if (entry.children) { + await this.copyEntriesRecursive( + entry.children, + sourcePath, + targetPath, + ); + } + } else { + const content = await this.readFile(sourceEntryPath); + await this.createFile(targetEntryPath, content); + } + } + } + + private createFileSystemError( + message: string, + originalError: unknown, + ): CodebotError { + return { + type: ErrorType.FILE_SYSTEM_ERROR, + message, + details: originalError, + recoverable: false, + }; + } +} diff --git a/src/managers/ProjectDetector.ts b/src/managers/ProjectDetector.ts index d9f06a5..67f5a6b 100644 --- a/src/managers/ProjectDetector.ts +++ b/src/managers/ProjectDetector.ts @@ -1,163 +1,169 @@ -import * as path from 'path'; -import * as vscode from 'vscode'; -import { IProjectDetector, ProjectContext } from '../types'; -import { CodebotError } from '../errors'; -import { DEFAULT_TEMPLATE_FOLDER, CONFIG_FILE_NAME } from '../constants'; - -export class ProjectDetector implements IProjectDetector { - private workspaceRoot: string; - private projectCache = new Map(); - - constructor() { - this.workspaceRoot = this.getWorkspaceRoot(); - } - - async detectProject(folderPath: string): Promise { - try { - // Check cache first - const cacheKey = this.normalizePath(folderPath); - if (this.projectCache.has(cacheKey)) { - return this.projectCache.get(cacheKey)!; - } - - const projectRoot = this.resolveProjectRoot(folderPath); - const isMultiProject = this.isMultiProjectWorkspace(); - - const context: ProjectContext = { - workspaceRoot: this.workspaceRoot, - projectRoot, - templatePath: this.resolveTemplatePath(projectRoot), - configPath: this.resolveConfigPath(projectRoot), - isMultiProject, - projectName: isMultiProject ? path.basename(projectRoot) : undefined - }; - - // Cache the result - this.projectCache.set(cacheKey, context); - return context; - } catch (error) { - throw CodebotError.projectDetectionError(folderPath, error as Error); - } - } - - isMultiProjectWorkspace(): boolean { - if (!vscode.workspace.workspaceFolders) { - return false; - } - - // If there are multiple workspace folders, it's definitely multi-project - if (vscode.workspace.workspaceFolders.length > 1) { - return true; - } - - // Check if the single workspace contains multiple project-like folders - return this.detectSubProjects(); - } - - resolveProjectRoot(folderPath: string): string { - const normalizedPath = this.normalizePath(folderPath); - - // If it's a multi-project workspace, find the nearest project root - if (this.isMultiProjectWorkspace()) { - return this.findNearestProjectRoot(normalizedPath); - } - - // For single project, the workspace root is the project root - return this.workspaceRoot; - } - - private getWorkspaceRoot(): string { - const workspaceFolders = vscode.workspace.workspaceFolders; - if (!workspaceFolders || workspaceFolders.length === 0) { - throw CodebotError.workspaceNotFound(); - } - return workspaceFolders[0].uri.fsPath; - } - - private normalizePath(filePath: string): string { - return path.resolve(filePath).replace(/\\/g, '/'); - } - - private resolveTemplatePath(projectRoot: string): string { - return path.join(projectRoot, DEFAULT_TEMPLATE_FOLDER); - } - - private resolveConfigPath(projectRoot: string): string { - return path.join(projectRoot, CONFIG_FILE_NAME); - } - - private detectSubProjects(): boolean { - try { - const fs = require('fs'); - const workspaceContents = fs.readdirSync(this.workspaceRoot, { withFileTypes: true }); - - // Look for multiple directories that could be projects - const potentialProjects = workspaceContents - .filter((dirent: any) => dirent.isDirectory()) - .filter((dirent: any) => !dirent.name.startsWith('.')) - .filter((dirent: any) => this.looksLikeProject(path.join(this.workspaceRoot, dirent.name))); - - return potentialProjects.length > 1; - } catch (error) { - // If we can't read the workspace, assume single project - return false; - } - } - - private looksLikeProject(dirPath: string): boolean { - try { - const fs = require('fs'); - const contents = fs.readdirSync(dirPath); - - // Check for common project indicators - const projectIndicators = [ - 'package.json', - 'tsconfig.json', - 'src', - 'lib', - 'templates', - CONFIG_FILE_NAME - ]; - - return projectIndicators.some(indicator => contents.includes(indicator)); - } catch (error) { - return false; - } - } - - private findNearestProjectRoot(folderPath: string): string { - let currentPath = folderPath; - - // Walk up the directory tree until we find a project root or reach workspace root - while (currentPath !== this.workspaceRoot && currentPath !== path.dirname(currentPath)) { - if (this.looksLikeProject(currentPath)) { - return currentPath; - } - currentPath = path.dirname(currentPath); - } - - // If we reach workspace root and it looks like a project, use it - if (this.looksLikeProject(this.workspaceRoot)) { - return this.workspaceRoot; - } - - // Otherwise, try to find the nearest sibling project - const parentDir = path.dirname(folderPath); - if (parentDir !== this.workspaceRoot) { - return this.findNearestProjectRoot(parentDir); - } - - // Fallback to workspace root - return this.workspaceRoot; - } - - // Method to clear cache when workspace changes - clearCache(): void { - this.projectCache.clear(); - } - - // Method to get all detected projects - getAllProjects(): ProjectContext[] { - return Array.from(this.projectCache.values()); - } -} \ No newline at end of file +import { type Dirent, readdirSync } from 'node:fs'; +import * as path from 'node:path'; +import * as vscode from 'vscode'; +import { CONFIG_FILE_NAME, DEFAULT_TEMPLATE_FOLDER } from '../constants'; +import { CodebotError } from '../errors'; +import type { IProjectDetector, ProjectContext } from '../types'; + +export class ProjectDetector implements IProjectDetector { + private workspaceRoot: string; + private projectCache = new Map(); + + constructor() { + this.workspaceRoot = this.getWorkspaceRoot(); + } + + async detectProject(folderPath: string): Promise { + try { + // Check cache first + const cacheKey = this.normalizePath(folderPath); + if (this.projectCache.has(cacheKey)) { + return this.projectCache.get(cacheKey) as ProjectContext; + } + + const projectRoot = this.resolveProjectRoot(folderPath); + const isMultiProject = this.isMultiProjectWorkspace(); + + const context: ProjectContext = { + workspaceRoot: this.workspaceRoot, + projectRoot, + templatePath: this.resolveTemplatePath(projectRoot), + configPath: this.resolveConfigPath(projectRoot), + isMultiProject, + projectName: isMultiProject ? path.basename(projectRoot) : undefined, + }; + + // Cache the result + this.projectCache.set(cacheKey, context); + return context; + } catch (error) { + throw CodebotError.projectDetectionError(folderPath, error as Error); + } + } + + isMultiProjectWorkspace(): boolean { + if (!vscode.workspace.workspaceFolders) { + return false; + } + + // If there are multiple workspace folders, it's definitely multi-project + if (vscode.workspace.workspaceFolders.length > 1) { + return true; + } + + // Check if the single workspace contains multiple project-like folders + return this.detectSubProjects(); + } + + resolveProjectRoot(folderPath: string): string { + const normalizedPath = this.normalizePath(folderPath); + + // If it's a multi-project workspace, find the nearest project root + if (this.isMultiProjectWorkspace()) { + return this.findNearestProjectRoot(normalizedPath); + } + + // For single project, the workspace root is the project root + return this.workspaceRoot; + } + + private getWorkspaceRoot(): string { + const workspaceFolders = vscode.workspace.workspaceFolders; + if (!workspaceFolders || workspaceFolders.length === 0) { + throw CodebotError.workspaceNotFound(); + } + return workspaceFolders[0].uri.fsPath; + } + + private normalizePath(filePath: string): string { + return path.resolve(filePath).replace(/\\/g, '/'); + } + + private resolveTemplatePath(projectRoot: string): string { + return path.join(projectRoot, DEFAULT_TEMPLATE_FOLDER); + } + + private resolveConfigPath(projectRoot: string): string { + return path.join(projectRoot, CONFIG_FILE_NAME); + } + + private detectSubProjects(): boolean { + try { + const workspaceContents = readdirSync(this.workspaceRoot, { + withFileTypes: true, + }); + + // Look for multiple directories that could be projects + const potentialProjects = workspaceContents + .filter((dirent: Dirent) => dirent.isDirectory()) + .filter((dirent: Dirent) => !dirent.name.startsWith('.')) + .filter((dirent: Dirent) => + this.looksLikeProject(path.join(this.workspaceRoot, dirent.name)), + ); + + return potentialProjects.length > 1; + } catch (_error) { + // If we can't read the workspace, assume single project + return false; + } + } + + private looksLikeProject(dirPath: string): boolean { + try { + const contents = readdirSync(dirPath); + + // Check for common project indicators + const projectIndicators = [ + 'package.json', + 'tsconfig.json', + 'src', + 'lib', + 'templates', + CONFIG_FILE_NAME, + ]; + + return projectIndicators.some(indicator => contents.includes(indicator)); + } catch (_error) { + return false; + } + } + + private findNearestProjectRoot(folderPath: string): string { + let currentPath = folderPath; + + // Walk up the directory tree until we find a project root or reach workspace root + while ( + currentPath !== this.workspaceRoot && + currentPath !== path.dirname(currentPath) + ) { + if (this.looksLikeProject(currentPath)) { + return currentPath; + } + currentPath = path.dirname(currentPath); + } + + // If we reach workspace root and it looks like a project, use it + if (this.looksLikeProject(this.workspaceRoot)) { + return this.workspaceRoot; + } + + // Otherwise, try to find the nearest sibling project + const parentDir = path.dirname(folderPath); + if (parentDir !== this.workspaceRoot) { + return this.findNearestProjectRoot(parentDir); + } + + // Fallback to workspace root + return this.workspaceRoot; + } + + // Method to clear cache when workspace changes + clearCache(): void { + this.projectCache.clear(); + } + + // Method to get all detected projects + getAllProjects(): ProjectContext[] { + return Array.from(this.projectCache.values()); + } +} diff --git a/src/managers/TemplateManager.ts b/src/managers/TemplateManager.ts index e8172a8..1e109ee 100644 --- a/src/managers/TemplateManager.ts +++ b/src/managers/TemplateManager.ts @@ -1,559 +1,690 @@ -import * as path from 'path'; -import * as handlebars from 'handlebars'; -import { ITemplateManager, TemplateType, TemplateStructure, ProcessedTemplate, ProcessedFile, ProcessedDirectory } from '../types'; -import { CodebotError } from '../errors'; -import { TEMPLATE_FILE_EXTENSION, DEFAULT_TEMPLATE_FOLDER } from '../constants'; -import { ValidationUtils } from '../utils/validation'; -import { DirectoryProcessor } from '../utils/DirectoryProcessor'; -import { FileSystemManager } from './FileSystemManager'; - -interface TemplateCache { - templates: TemplateType[]; - timestamp: number; - projectPath: string; -} - -interface CompiledTemplateCache { - compiled: HandlebarsTemplateDelegate; - timestamp: number; -} - -export class TemplateManager implements ITemplateManager { - private templateCache = new Map(); - private compiledCache = new Map(); - private readonly CACHE_TTL = 5 * 60 * 1000; // 5 minutes - private directoryProcessor: DirectoryProcessor; - private fileSystemManager: FileSystemManager; - - constructor(fileSystemManager?: FileSystemManager) { - this.fileSystemManager = fileSystemManager || new FileSystemManager(); - this.directoryProcessor = new DirectoryProcessor(this.fileSystemManager); - } - - async discoverTemplates(projectPath: string): Promise { - try { - // Check cache first - const cacheKey = this.normalizePath(projectPath); - const cached = this.templateCache.get(cacheKey); - - if (cached && this.isCacheValid(cached.timestamp)) { - return cached.templates; - } - - // Discover templates hierarchically - const templates = await this.discoverTemplatesHierarchically(projectPath); - - // Cache the results - this.templateCache.set(cacheKey, { - templates, - timestamp: Date.now(), - projectPath - }); - - return templates; - } catch (error) { - throw CodebotError.templateProcessingError(projectPath, error as Error); - } - } - - async processTemplate(templatePath: string, componentName: string): Promise { - try { - // Check compiled cache first (cache by template path only, since compiled template is the same regardless of component name) - const cached = this.compiledCache.get(templatePath); - - if (cached && this.isCacheValid(cached.timestamp)) { - return cached.compiled({ name: this.formatComponentName(componentName) }); - } - - // Read and compile template - const templateContent = await this.readTemplateFile(templatePath); - const compiled = handlebars.compile(templateContent); - - // Cache compiled template - this.compiledCache.set(templatePath, { - compiled, - timestamp: Date.now() - }); - - return compiled({ name: this.formatComponentName(componentName) }); - } catch (error) { - throw CodebotError.templateProcessingError(templatePath, error as Error); - } - } - - getTemplateFiles(templateType: string): string[] { - try { - const fs = require('fs'); - const files = fs.readdirSync(templateType); - - return files.filter((file: string) => ValidationUtils.isTemplateFile(file)); - } catch (error) { - throw CodebotError.templateProcessingError(templateType, error as Error); - } - } - - async scanTemplateStructure(templatePath: string): Promise { - try { - const structure = await this.directoryProcessor.scanTemplateStructure(templatePath); - - // Validate depth for security - this.directoryProcessor.validateDepth(structure); - - return structure; - } catch (error) { - throw CodebotError.templateProcessingError(templatePath, error as Error); - } - } - - async processTemplateHierarchy(templateStructure: TemplateStructure, componentName: string): Promise { - try { - // Extract template folder name from the root path - const templateFolderName = this.extractTemplateFolderName(templateStructure.rootPath); - - const processedFiles = await this.processFiles(templateStructure.files, componentName, '', templateFolderName); - const processedDirectories = await this.processDirectories(templateStructure.directories, componentName, '', templateFolderName); - - const processedTemplate = { - componentName, - files: processedFiles, - directories: processedDirectories - }; - - // Validate the processed template for integrity - this.validateProcessedTemplate(processedTemplate); - - return processedTemplate; - } catch (error) { - throw CodebotError.templateProcessingError(templateStructure.rootPath, error as Error); - } - } - - invalidateCache(): void { - this.templateCache.clear(); - this.compiledCache.clear(); - } - - getCacheStats(): { templateCacheSize: number; compiledCacheSize: number } { - return { - templateCacheSize: this.templateCache.size, - compiledCacheSize: this.compiledCache.size - }; - } - - invalidateProjectCache(projectPath: string): void { - const cacheKey = this.normalizePath(projectPath); - this.templateCache.delete(cacheKey); - } - - cleanExpiredCache(): void { - // Clean template cache - for (const [key, cache] of this.templateCache.entries()) { - if (!this.isCacheValid(cache.timestamp)) { - this.templateCache.delete(key); - } - } - - // Clean compiled cache - for (const [key, cache] of this.compiledCache.entries()) { - if (!this.isCacheValid(cache.timestamp)) { - this.compiledCache.delete(key); - } - } - } - - async preloadTemplates(templatePaths: string[]): Promise { - for (const templatePath of templatePaths) { - try { - await this.discoverTemplates(templatePath); - } catch (error) { - throw CodebotError.templateProcessingError(templatePath, error as Error); - } - } - } - - private async discoverTemplatesHierarchically(projectPath: string): Promise { - const templatePaths = this.getTemplatePaths(projectPath); - const allTemplates: TemplateType[] = []; - const seenTemplates = new Set(); - - // Search in order of priority: project -> workspace -> default - for (const templatePath of templatePaths) { - const templates = await this.discoverTemplatesInPath(templatePath); - - // Add only unique templates (by name) - for (const template of templates) { - if (!seenTemplates.has(template.name)) { - allTemplates.push(template); - seenTemplates.add(template.name); - } - } - } - - return allTemplates; - } - - private getTemplatePaths(projectPath: string): string[] { - const paths: string[] = []; - - // 1. Project-specific templates - const projectTemplatePath = path.join(projectPath, DEFAULT_TEMPLATE_FOLDER); - paths.push(projectTemplatePath); - - // 2. Workspace root templates (if different from project) - const workspaceRoot = this.findWorkspaceRoot(projectPath); - if (workspaceRoot && workspaceRoot !== projectPath) { - const workspaceTemplatePath = path.join(workspaceRoot, DEFAULT_TEMPLATE_FOLDER); - paths.push(workspaceTemplatePath); - } - - return paths; - } - - private async discoverTemplatesInPath(templatePath: string): Promise { - try { - // Check if template directory exists - if (!await this.fileSystemManager.folderExists(templatePath)) { - return []; // Directory doesn't exist - } - - const entries = await this.fileSystemManager.listDirectories(templatePath); - const templates: TemplateType[] = []; - - for (const entry of entries) { - const templateTypePath = path.join(templatePath, entry); - const files = await this.getTemplateFilesInDirectory(templateTypePath); - const structure = await this.scanTemplateStructure(templateTypePath); - const hasSubdirectories = structure.directories.length > 0; - - if (files.length > 0 || hasSubdirectories) { - templates.push({ - name: entry, - path: templateTypePath, - files, - hasSubdirectories, - structure - }); - } - } - - return templates; - } catch (error) { - // If we can't read the directory, return empty array - return []; - } - } - - private async getTemplateFilesInDirectory(directoryPath: string): Promise { - try { - const files = await this.fileSystemManager.listFiles(directoryPath); - return files.filter((file: string) => ValidationUtils.isTemplateFile(file)); - } catch (error) { - return []; - } - } - - private async readTemplateFile(templatePath: string): Promise { - try { - return await this.fileSystemManager.readFile(templatePath); - } catch (error) { - throw new Error(`Failed to read template file: ${templatePath}`); - } - } - - private formatComponentName(componentName: string): string { - // Convert to PascalCase, preserving existing case if already in PascalCase - if (/^[A-Z][a-zA-Z0-9]*$/.test(componentName)) { - return componentName; // Already in PascalCase - } - - return componentName - .split(/[-_\s]+/) - .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) - .join(''); - } - - private findWorkspaceRoot(projectPath: string): string | null { - try { - const vscode = require('vscode'); - const workspaceFolders = vscode.workspace.workspaceFolders; - - if (!workspaceFolders || workspaceFolders.length === 0) { - return null; - } - - // Find the workspace folder that contains the project path - for (const folder of workspaceFolders) { - const workspaceRoot = folder.uri.fsPath; - if (projectPath.startsWith(workspaceRoot)) { - return workspaceRoot; - } - } - - return workspaceFolders[0].uri.fsPath; - } catch (error) { - return null; - } - } - - private normalizePath(filePath: string): string { - return path.resolve(filePath).replace(/\\/g, '/'); - } - - private extractTemplateFolderName(templateRootPath: string): string | undefined { - try { - if (!templateRootPath || typeof templateRootPath !== 'string') { - return undefined; - } - - // Handle both Unix and Windows path separators - const normalizedPath = templateRootPath.replace(/\\/g, '/'); - const pathParts = normalizedPath.split('/').filter(part => part.length > 0); - - if (pathParts.length === 0) { - return undefined; - } - - const folderName = pathParts[pathParts.length - 1]; - - // Return undefined if we can't determine a valid folder name - if (!folderName || folderName === '.' || folderName === '..') { - return undefined; - } - - return folderName; - } catch (error) { - // If there's any error extracting the folder name, return undefined - return undefined; - } - } - - private isCacheValid(timestamp: number): boolean { - return Date.now() - timestamp < this.CACHE_TTL; - } - - private async processFiles(files: any[], componentName: string, basePath: string = '', templateFolderName?: string): Promise { - const processedFiles: ProcessedFile[] = []; - - for (const file of files) { - if (ValidationUtils.isTemplateFile(file.name)) { - const content = await this.fileSystemManager.readFile(file.path); - const processedContent = this.processTemplateContent(content, componentName); - const targetPath = this.generateTargetPath(file.relativePath, componentName, basePath, templateFolderName); - - processedFiles.push({ - originalPath: file.path, - targetPath, - content: processedContent - }); - } - } - - return processedFiles; - } - - private async processDirectories(directories: any[], componentName: string, basePath: string = '', templateFolderName?: string): Promise { - const processedDirectories: ProcessedDirectory[] = []; - - for (const directory of directories) { - // Build the accumulated path for this directory - const currentDirectoryPath = basePath ? path.join(basePath, directory.relativePath) : directory.relativePath; - - // Process files and subdirectories with the accumulated path - const processedFiles = await this.processFiles(directory.files, componentName, currentDirectoryPath, templateFolderName); - const processedSubdirectories = await this.processDirectories(directory.subdirectories, componentName, currentDirectoryPath, templateFolderName); - const targetPath = this.generateTargetPath(directory.relativePath, componentName, basePath, templateFolderName); - - processedDirectories.push({ - originalPath: directory.path, - targetPath, - files: processedFiles, - subdirectories: processedSubdirectories - }); - } - - return processedDirectories; - } - - private processTemplateContent(content: string, componentName: string): string { - try { - const compiled = handlebars.compile(content); - return compiled({ name: this.formatComponentName(componentName) }); - } catch (error) { - throw new Error(`Failed to process template content: ${error}`); - } - } - - private generateTargetPath(relativePath: string, componentName: string, basePath: string = '', templateFolderName?: string): string { - // Validate the input relativePath first to prevent path traversal attacks - this.validateTargetPath(relativePath); - - // Validate basePath if provided - if (basePath) { - this.validateTargetPath(basePath); - } - - // Remove .hbs extension and apply naming rules - let targetPath = relativePath; - - if (targetPath.endsWith(TEMPLATE_FILE_EXTENSION)) { - targetPath = targetPath.slice(0, -TEMPLATE_FILE_EXTENSION.length); - } - - // Apply component naming rules to the path - const processedPath = this.applyNamingRules(targetPath, componentName, templateFolderName); - - // Combine basePath with processedPath using path.join for cross-platform compatibility - // path.join handles empty strings gracefully and ensures proper path separators - const finalPath = basePath ? path.join(basePath, processedPath) : processedPath; - - // Validate the final path as well to ensure no issues after processing - this.validateTargetPath(finalPath); - - return finalPath; - } - - private applyNamingRules(filePath: string, componentName: string, templateFolderName?: string): string { - const pathParts = filePath.split(path.sep); - const fileName = pathParts[pathParts.length - 1]; - let newFileName = fileName; - - // First, check if the filename starts with the template folder name (case-insensitive) - if (templateFolderName && fileName.toLowerCase().startsWith(templateFolderName.toLowerCase())) { - // Replace the matched portion with the component name, preserving the case of the component name - const matchedPortion = fileName.substring(0, templateFolderName.length); - const remainingPortion = fileName.substring(templateFolderName.length); - newFileName = this.formatComponentName(componentName) + remainingPortion; - } - - // Then, apply existing template/Template replacement functionality - if (newFileName.includes('template') || newFileName.includes('Template')) { - newFileName = newFileName - .replace(/template/gi, componentName.toLowerCase()) - .replace(/Template/g, this.formatComponentName(componentName)); - } - - pathParts[pathParts.length - 1] = newFileName; - return pathParts.join(path.sep); - } - - private validateTargetPath(targetPath: string): void { - // Skip validation for empty strings - if (!targetPath) { - return; - } - - // Prevent path traversal attacks - check for .. sequences - if (targetPath.includes('..')) { - throw new Error(`Invalid target path - contains path traversal: ${targetPath}`); - } - - // Prevent absolute paths - if (path.isAbsolute(targetPath)) { - throw new Error(`Invalid target path - absolute paths not allowed: ${targetPath}`); - } - - // Check for Windows and Unix absolute path patterns explicitly - if (targetPath.startsWith('/') || /^[a-zA-Z]:/.test(targetPath)) { - throw new Error(`Invalid target path - absolute path detected: ${targetPath}`); - } - - // Normalize the path and check for any remaining path traversal attempts - const normalizedPath = path.normalize(targetPath); - - // After normalization, check again for path traversal and absolute paths - if (normalizedPath.includes('..')) { - throw new Error(`Invalid target path - path traversal detected after normalization: ${normalizedPath}`); - } - - if (path.isAbsolute(normalizedPath)) { - throw new Error(`Invalid target path - absolute path detected after normalization: ${normalizedPath}`); - } - - // Additional check for paths that start with path separators after normalization - if (normalizedPath.startsWith('/') || normalizedPath.startsWith('\\')) { - throw new Error(`Invalid target path - starts with path separator after normalization: ${normalizedPath}`); - } - - // Check for Windows drive letters after normalization - if (/^[a-zA-Z]:/.test(normalizedPath)) { - throw new Error(`Invalid target path - Windows drive letter detected after normalization: ${normalizedPath}`); - } - } - - /** - * Validates the processed template for integrity issues like duplicate target paths - * @param processedTemplate The processed template to validate - * @throws Error if validation fails - */ - private validateProcessedTemplate(processedTemplate: ProcessedTemplate): void { - const allTargetPaths = this.collectAllTargetPaths(processedTemplate); - const duplicates = this.findDuplicatePaths(allTargetPaths); - - if (duplicates.length > 0) { - throw new Error(`Duplicate target paths detected: ${duplicates.join(', ')}`); - } - } - - /** - * Collects all target paths from a processed template, including files in nested directories - * @param processedTemplate The processed template to collect paths from - * @returns Array of all target paths - */ - private collectAllTargetPaths(processedTemplate: ProcessedTemplate): string[] { - const allPaths: string[] = []; - - // Collect paths from root-level files - for (const file of processedTemplate.files) { - allPaths.push(file.targetPath); - } - - // Recursively collect paths from directories - this.collectPathsFromDirectories(processedTemplate.directories, allPaths); - - return allPaths; - } - - /** - * Recursively collects target paths from directories and their subdirectories - * @param directories Array of processed directories to collect paths from - * @param allPaths Array to accumulate all paths - */ - private collectPathsFromDirectories(directories: ProcessedDirectory[], allPaths: string[]): void { - for (const directory of directories) { - // Collect paths from files in this directory - for (const file of directory.files) { - allPaths.push(file.targetPath); - } - - // Recursively collect paths from subdirectories - if (directory.subdirectories.length > 0) { - this.collectPathsFromDirectories(directory.subdirectories, allPaths); - } - } - } - - /** - * Finds duplicate paths in an array of target paths - * @param targetPaths Array of target paths to check for duplicates - * @returns Array of duplicate paths - */ - private findDuplicatePaths(targetPaths: string[]): string[] { - const pathCounts = new Map(); - const duplicates: string[] = []; - - // Count occurrences of each path - for (const path of targetPaths) { - const count = pathCounts.get(path) || 0; - pathCounts.set(path, count + 1); - } - - // Find paths that appear more than once - for (const [path, count] of pathCounts.entries()) { - if (count > 1) { - duplicates.push(path); - } - } - - return duplicates; - } -} \ No newline at end of file +import * as path from 'node:path'; +import * as handlebars from 'handlebars'; +import { DEFAULT_TEMPLATE_FOLDER, TEMPLATE_FILE_EXTENSION } from '../constants'; +import { CodebotError } from '../errors'; +import type { + ITemplateManager, + ProcessedDirectory, + ProcessedFile, + ProcessedTemplate, + TemplateDirectoryInfo, + TemplateFileInfo, + TemplateStructure, + TemplateType, +} from '../types'; +import { DirectoryProcessor } from '../utils/DirectoryProcessor'; +import { isTemplateFile } from '../utils/validation'; +import { FileSystemManager } from './FileSystemManager'; + +interface TemplateCache { + templates: TemplateType[]; + timestamp: number; + projectPath: string; +} + +interface CompiledTemplateCache { + compiled: HandlebarsTemplateDelegate; + timestamp: number; +} + +export class TemplateManager implements ITemplateManager { + private templateCache = new Map(); + private compiledCache = new Map(); + private readonly CACHE_TTL = 5 * 60 * 1000; // 5 minutes + private directoryProcessor: DirectoryProcessor; + private fileSystemManager: FileSystemManager; + + constructor(fileSystemManager?: FileSystemManager) { + this.fileSystemManager = fileSystemManager || new FileSystemManager(); + this.directoryProcessor = new DirectoryProcessor(this.fileSystemManager); + } + + async discoverTemplates(projectPath: string): Promise { + try { + // Check cache first + const cacheKey = this.normalizePath(projectPath); + const cached = this.templateCache.get(cacheKey); + + if (cached && this.isCacheValid(cached.timestamp)) { + return cached.templates; + } + + // Discover templates hierarchically + const templates = await this.discoverTemplatesHierarchically(projectPath); + + // Cache the results + this.templateCache.set(cacheKey, { + templates, + timestamp: Date.now(), + projectPath, + }); + + return templates; + } catch (error) { + throw CodebotError.templateProcessingError(projectPath, error as Error); + } + } + + async processTemplate( + templatePath: string, + componentName: string, + ): Promise { + try { + // Check compiled cache first (cache by template path only, since compiled template is the same regardless of component name) + const cached = this.compiledCache.get(templatePath); + + if (cached && this.isCacheValid(cached.timestamp)) { + return cached.compiled({ + name: this.formatComponentName(componentName), + }); + } + + // Read and compile template + const templateContent = await this.readTemplateFile(templatePath); + const compiled = handlebars.compile(templateContent); + + // Cache compiled template + this.compiledCache.set(templatePath, { + compiled, + timestamp: Date.now(), + }); + + return compiled({ name: this.formatComponentName(componentName) }); + } catch (error) { + throw CodebotError.templateProcessingError(templatePath, error as Error); + } + } + + getTemplateFiles(templateType: string): string[] { + try { + const fs = require('node:fs'); + const files = fs.readdirSync(templateType); + + return files.filter((file: string) => isTemplateFile(file)); + } catch (error) { + throw CodebotError.templateProcessingError(templateType, error as Error); + } + } + + async scanTemplateStructure( + templatePath: string, + ): Promise { + try { + const structure = + await this.directoryProcessor.scanTemplateStructure(templatePath); + + // Validate depth for security + this.directoryProcessor.validateDepth(structure); + + return structure; + } catch (error) { + throw CodebotError.templateProcessingError(templatePath, error as Error); + } + } + + async processTemplateHierarchy( + templateStructure: TemplateStructure, + componentName: string, + ): Promise { + try { + // Extract template folder name from the root path + const templateFolderName = this.extractTemplateFolderName( + templateStructure.rootPath, + ); + + const processedFiles = await this.processFiles( + templateStructure.files, + componentName, + '', + templateFolderName, + ); + const processedDirectories = await this.processDirectories( + templateStructure.directories, + componentName, + '', + templateFolderName, + ); + + const processedTemplate = { + componentName, + files: processedFiles, + directories: processedDirectories, + }; + + // Validate the processed template for integrity + this.validateProcessedTemplate(processedTemplate); + + return processedTemplate; + } catch (error) { + throw CodebotError.templateProcessingError( + templateStructure.rootPath, + error as Error, + ); + } + } + + invalidateCache(): void { + this.templateCache.clear(); + this.compiledCache.clear(); + } + + getCacheStats(): { templateCacheSize: number; compiledCacheSize: number } { + return { + templateCacheSize: this.templateCache.size, + compiledCacheSize: this.compiledCache.size, + }; + } + + invalidateProjectCache(projectPath: string): void { + const cacheKey = this.normalizePath(projectPath); + this.templateCache.delete(cacheKey); + } + + cleanExpiredCache(): void { + // Clean template cache + for (const [key, cache] of this.templateCache.entries()) { + if (!this.isCacheValid(cache.timestamp)) { + this.templateCache.delete(key); + } + } + + // Clean compiled cache + for (const [key, cache] of this.compiledCache.entries()) { + if (!this.isCacheValid(cache.timestamp)) { + this.compiledCache.delete(key); + } + } + } + + async preloadTemplates(templatePaths: string[]): Promise { + for (const templatePath of templatePaths) { + try { + await this.discoverTemplates(templatePath); + } catch (error) { + throw CodebotError.templateProcessingError( + templatePath, + error as Error, + ); + } + } + } + + private async discoverTemplatesHierarchically( + projectPath: string, + ): Promise { + const templatePaths = this.getTemplatePaths(projectPath); + const allTemplates: TemplateType[] = []; + const seenTemplates = new Set(); + + // Search in order of priority: project -> workspace -> default + for (const templatePath of templatePaths) { + const templates = await this.discoverTemplatesInPath(templatePath); + + // Add only unique templates (by name) + for (const template of templates) { + if (!seenTemplates.has(template.name)) { + allTemplates.push(template); + seenTemplates.add(template.name); + } + } + } + + return allTemplates; + } + + private getTemplatePaths(projectPath: string): string[] { + const paths: string[] = []; + + // 1. Project-specific templates + const projectTemplatePath = path.join(projectPath, DEFAULT_TEMPLATE_FOLDER); + paths.push(projectTemplatePath); + + // 2. Workspace root templates (if different from project) + const workspaceRoot = this.findWorkspaceRoot(projectPath); + if (workspaceRoot && workspaceRoot !== projectPath) { + const workspaceTemplatePath = path.join( + workspaceRoot, + DEFAULT_TEMPLATE_FOLDER, + ); + paths.push(workspaceTemplatePath); + } + + return paths; + } + + private async discoverTemplatesInPath( + templatePath: string, + ): Promise { + try { + // Check if template directory exists + if (!(await this.fileSystemManager.folderExists(templatePath))) { + return []; // Directory doesn't exist + } + + const entries = + await this.fileSystemManager.listDirectories(templatePath); + const templates: TemplateType[] = []; + + for (const entry of entries) { + const templateTypePath = path.join(templatePath, entry); + const files = await this.getTemplateFilesInDirectory(templateTypePath); + const structure = await this.scanTemplateStructure(templateTypePath); + const hasSubdirectories = structure.directories.length > 0; + + if (files.length > 0 || hasSubdirectories) { + templates.push({ + name: entry, + path: templateTypePath, + files, + hasSubdirectories, + structure, + }); + } + } + + return templates; + } catch (_error) { + // If we can't read the directory, return empty array + return []; + } + } + + private async getTemplateFilesInDirectory( + directoryPath: string, + ): Promise { + try { + const files = await this.fileSystemManager.listFiles(directoryPath); + return files.filter((file: string) => isTemplateFile(file)); + } catch (_error) { + return []; + } + } + + private async readTemplateFile(templatePath: string): Promise { + try { + return await this.fileSystemManager.readFile(templatePath); + } catch (_error) { + throw new Error(`Failed to read template file: ${templatePath}`); + } + } + + private formatComponentName(componentName: string): string { + // Convert to PascalCase, preserving existing case if already in PascalCase + if (/^[A-Z][a-zA-Z0-9]*$/.test(componentName)) { + return componentName; // Already in PascalCase + } + + return componentName + .split(/[-_\s]+/) + .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join(''); + } + + private findWorkspaceRoot(projectPath: string): string | null { + try { + const vscode = require('vscode'); + const workspaceFolders = vscode.workspace.workspaceFolders; + + if (!workspaceFolders || workspaceFolders.length === 0) { + return null; + } + + // Find the workspace folder that contains the project path + for (const folder of workspaceFolders) { + const workspaceRoot = folder.uri.fsPath; + if (projectPath.startsWith(workspaceRoot)) { + return workspaceRoot; + } + } + + return workspaceFolders[0].uri.fsPath; + } catch (_error) { + return null; + } + } + + private normalizePath(filePath: string): string { + return path.resolve(filePath).replace(/\\/g, '/'); + } + + private extractTemplateFolderName( + templateRootPath: string, + ): string | undefined { + try { + if (!templateRootPath || typeof templateRootPath !== 'string') { + return undefined; + } + + // Handle both Unix and Windows path separators + const normalizedPath = templateRootPath.replace(/\\/g, '/'); + const pathParts = normalizedPath + .split('/') + .filter(part => part.length > 0); + + if (pathParts.length === 0) { + return undefined; + } + + const folderName = pathParts[pathParts.length - 1]; + + // Return undefined if we can't determine a valid folder name + if (!folderName || folderName === '.' || folderName === '..') { + return undefined; + } + + return folderName; + } catch (_error) { + // If there's any error extracting the folder name, return undefined + return undefined; + } + } + + private isCacheValid(timestamp: number): boolean { + return Date.now() - timestamp < this.CACHE_TTL; + } + + private async processFiles( + files: TemplateFileInfo[], + componentName: string, + basePath = '', + templateFolderName?: string, + ): Promise { + const processedFiles: ProcessedFile[] = []; + + for (const file of files) { + if (isTemplateFile(file.name)) { + const content = await this.fileSystemManager.readFile(file.path); + const processedContent = this.processTemplateContent( + content, + componentName, + ); + const targetPath = this.generateTargetPath( + file.relativePath, + componentName, + basePath, + templateFolderName, + ); + + processedFiles.push({ + originalPath: file.path, + targetPath, + content: processedContent, + }); + } + } + + return processedFiles; + } + + private async processDirectories( + directories: TemplateDirectoryInfo[], + componentName: string, + basePath = '', + templateFolderName?: string, + ): Promise { + const processedDirectories: ProcessedDirectory[] = []; + + for (const directory of directories) { + // Build the accumulated path for this directory + const currentDirectoryPath = basePath + ? path.join(basePath, directory.relativePath) + : directory.relativePath; + + // Process files and subdirectories with the accumulated path + const processedFiles = await this.processFiles( + directory.files, + componentName, + currentDirectoryPath, + templateFolderName, + ); + const processedSubdirectories = await this.processDirectories( + directory.subdirectories, + componentName, + currentDirectoryPath, + templateFolderName, + ); + const targetPath = this.generateTargetPath( + directory.relativePath, + componentName, + basePath, + templateFolderName, + ); + + processedDirectories.push({ + originalPath: directory.path, + targetPath, + files: processedFiles, + subdirectories: processedSubdirectories, + }); + } + + return processedDirectories; + } + + private processTemplateContent( + content: string, + componentName: string, + ): string { + try { + const compiled = handlebars.compile(content); + return compiled({ name: this.formatComponentName(componentName) }); + } catch (error) { + throw new Error(`Failed to process template content: ${error}`); + } + } + + private generateTargetPath( + relativePath: string, + componentName: string, + basePath = '', + templateFolderName?: string, + ): string { + // Validate the input relativePath first to prevent path traversal attacks + this.validateTargetPath(relativePath); + + // Validate basePath if provided + if (basePath) { + this.validateTargetPath(basePath); + } + + // Remove .hbs extension and apply naming rules + let targetPath = relativePath; + + if (targetPath.endsWith(TEMPLATE_FILE_EXTENSION)) { + targetPath = targetPath.slice(0, -TEMPLATE_FILE_EXTENSION.length); + } + + // Apply component naming rules to the path + const processedPath = this.applyNamingRules( + targetPath, + componentName, + templateFolderName, + ); + + // Combine basePath with processedPath using path.join for cross-platform compatibility + // path.join handles empty strings gracefully and ensures proper path separators + const finalPath = basePath + ? path.join(basePath, processedPath) + : processedPath; + + // Validate the final path as well to ensure no issues after processing + this.validateTargetPath(finalPath); + + return finalPath; + } + + private applyNamingRules( + filePath: string, + componentName: string, + templateFolderName?: string, + ): string { + const pathParts = filePath.split(path.sep); + const fileName = pathParts[pathParts.length - 1]; + let newFileName = fileName; + + // First, check if the filename starts with the template folder name (case-insensitive) + if ( + templateFolderName && + fileName.toLowerCase().startsWith(templateFolderName.toLowerCase()) + ) { + // Replace the matched portion with the component name, preserving the case of the component name + const _matchedPortion = fileName.substring(0, templateFolderName.length); + const remainingPortion = fileName.substring(templateFolderName.length); + newFileName = this.formatComponentName(componentName) + remainingPortion; + } + + // Then, apply existing template/Template replacement functionality + if (newFileName.includes('template') || newFileName.includes('Template')) { + newFileName = newFileName + .replace(/template/gi, componentName.toLowerCase()) + .replace(/Template/g, this.formatComponentName(componentName)); + } + + pathParts[pathParts.length - 1] = newFileName; + return pathParts.join(path.sep); + } + + private validateTargetPath(targetPath: string): void { + // Skip validation for empty strings + if (!targetPath) { + return; + } + + // Prevent path traversal attacks - check for .. sequences + if (targetPath.includes('..')) { + throw new Error( + `Invalid target path - contains path traversal: ${targetPath}`, + ); + } + + // Prevent absolute paths + if (path.isAbsolute(targetPath)) { + throw new Error( + `Invalid target path - absolute paths not allowed: ${targetPath}`, + ); + } + + // Check for Windows and Unix absolute path patterns explicitly + if (targetPath.startsWith('/') || /^[a-zA-Z]:/.test(targetPath)) { + throw new Error( + `Invalid target path - absolute path detected: ${targetPath}`, + ); + } + + // Normalize the path and check for any remaining path traversal attempts + const normalizedPath = path.normalize(targetPath); + + // After normalization, check again for path traversal and absolute paths + if (normalizedPath.includes('..')) { + throw new Error( + `Invalid target path - path traversal detected after normalization: ${normalizedPath}`, + ); + } + + if (path.isAbsolute(normalizedPath)) { + throw new Error( + `Invalid target path - absolute path detected after normalization: ${normalizedPath}`, + ); + } + + // Additional check for paths that start with path separators after normalization + if (normalizedPath.startsWith('/') || normalizedPath.startsWith('\\')) { + throw new Error( + `Invalid target path - starts with path separator after normalization: ${normalizedPath}`, + ); + } + + // Check for Windows drive letters after normalization + if (/^[a-zA-Z]:/.test(normalizedPath)) { + throw new Error( + `Invalid target path - Windows drive letter detected after normalization: ${normalizedPath}`, + ); + } + } + + /** + * Validates the processed template for integrity issues like duplicate target paths + * @param processedTemplate The processed template to validate + * @throws Error if validation fails + */ + private validateProcessedTemplate( + processedTemplate: ProcessedTemplate, + ): void { + const allTargetPaths = this.collectAllTargetPaths(processedTemplate); + const duplicates = this.findDuplicatePaths(allTargetPaths); + + if (duplicates.length > 0) { + throw new Error( + `Duplicate target paths detected: ${duplicates.join(', ')}`, + ); + } + } + + /** + * Collects all target paths from a processed template, including files in nested directories + * @param processedTemplate The processed template to collect paths from + * @returns Array of all target paths + */ + private collectAllTargetPaths( + processedTemplate: ProcessedTemplate, + ): string[] { + const allPaths: string[] = []; + + // Collect paths from root-level files + for (const file of processedTemplate.files) { + allPaths.push(file.targetPath); + } + + // Recursively collect paths from directories + this.collectPathsFromDirectories(processedTemplate.directories, allPaths); + + return allPaths; + } + + /** + * Recursively collects target paths from directories and their subdirectories + * @param directories Array of processed directories to collect paths from + * @param allPaths Array to accumulate all paths + */ + private collectPathsFromDirectories( + directories: ProcessedDirectory[], + allPaths: string[], + ): void { + for (const directory of directories) { + // Collect paths from files in this directory + for (const file of directory.files) { + allPaths.push(file.targetPath); + } + + // Recursively collect paths from subdirectories + if (directory.subdirectories.length > 0) { + this.collectPathsFromDirectories(directory.subdirectories, allPaths); + } + } + } + + /** + * Finds duplicate paths in an array of target paths + * @param targetPaths Array of target paths to check for duplicates + * @returns Array of duplicate paths + */ + private findDuplicatePaths(targetPaths: string[]): string[] { + const pathCounts = new Map(); + const duplicates: string[] = []; + + // Count occurrences of each path + for (const path of targetPaths) { + const count = pathCounts.get(path) || 0; + pathCounts.set(path, count + 1); + } + + // Find paths that appear more than once + for (const [path, count] of pathCounts.entries()) { + if (count > 1) { + duplicates.push(path); + } + } + + return duplicates; + } +} diff --git a/src/managers/index.ts b/src/managers/index.ts index 047bf5f..9b9a822 100644 --- a/src/managers/index.ts +++ b/src/managers/index.ts @@ -1,4 +1,4 @@ -export * from './ProjectDetector'; -export * from './ConfigurationManager'; -export * from './TemplateManager'; -export * from './FileSystemManager'; \ No newline at end of file +export * from './ConfigurationManager'; +export * from './FileSystemManager'; +export * from './ProjectDetector'; +export * from './TemplateManager'; diff --git a/src/test/__mocks__/vscode.js b/src/test/__mocks__/vscode.js index 4acd908..aab0228 100644 --- a/src/test/__mocks__/vscode.js +++ b/src/test/__mocks__/vscode.js @@ -1,51 +1,51 @@ -// Mock do módulo vscode para testes -const vscode = { - workspace: { - workspaceFolders: [ - { - uri: { fsPath: '/test/workspace' }, - name: 'test-workspace', - index: 0 - } - ], - getConfiguration: jest.fn(() => ({ - get: jest.fn(), - update: jest.fn(), - has: jest.fn(() => true), - inspect: jest.fn() - })), - onDidChangeConfiguration: jest.fn(), - findFiles: jest.fn(() => Promise.resolve([])), - openTextDocument: jest.fn(), - saveAll: jest.fn() - }, - window: { - showInformationMessage: jest.fn(), - showErrorMessage: jest.fn(), - showWarningMessage: jest.fn(), - showInputBox: jest.fn(), - showQuickPick: jest.fn(), - createOutputChannel: jest.fn(() => ({ - appendLine: jest.fn(), - show: jest.fn(), - hide: jest.fn(), - dispose: jest.fn() - })) - }, - Uri: { - file: jest.fn((path) => ({ fsPath: path, scheme: 'file', path })), - parse: jest.fn((uri) => ({ fsPath: uri, scheme: 'file', path: uri })) - }, - commands: { - registerCommand: jest.fn(), - executeCommand: jest.fn() - }, - ExtensionContext: jest.fn(), - ConfigurationTarget: { - Global: 1, - Workspace: 2, - WorkspaceFolder: 3 - } -}; - -module.exports = vscode; \ No newline at end of file +// Mock do módulo vscode para testes +const vscode = { + workspace: { + workspaceFolders: [ + { + uri: { fsPath: '/test/workspace' }, + name: 'test-workspace', + index: 0, + }, + ], + getConfiguration: jest.fn(() => ({ + get: jest.fn(), + update: jest.fn(), + has: jest.fn(() => true), + inspect: jest.fn(), + })), + onDidChangeConfiguration: jest.fn(), + findFiles: jest.fn(() => Promise.resolve([])), + openTextDocument: jest.fn(), + saveAll: jest.fn(), + }, + window: { + showInformationMessage: jest.fn(), + showErrorMessage: jest.fn(), + showWarningMessage: jest.fn(), + showInputBox: jest.fn(), + showQuickPick: jest.fn(), + createOutputChannel: jest.fn(() => ({ + appendLine: jest.fn(), + show: jest.fn(), + hide: jest.fn(), + dispose: jest.fn(), + })), + }, + Uri: { + file: jest.fn(path => ({ fsPath: path, scheme: 'file', path })), + parse: jest.fn(uri => ({ fsPath: uri, scheme: 'file', path: uri })), + }, + commands: { + registerCommand: jest.fn(), + executeCommand: jest.fn(), + }, + ExtensionContext: jest.fn(), + ConfigurationTarget: { + Global: 1, + Workspace: 2, + WorkspaceFolder: 3, + }, +}; + +module.exports = vscode; diff --git a/src/test/commands/updateComponent.test.ts b/src/test/commands/updateComponent.test.ts index 39aa3b3..8f510ba 100644 --- a/src/test/commands/updateComponent.test.ts +++ b/src/test/commands/updateComponent.test.ts @@ -1,274 +1,288 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import * as os from 'os'; -import { updateComponent } from '../../commands/updateComponent'; -import { FileSystemManager } from '../../managers/FileSystemManager'; -import { CommandArgs, ErrorType } from '../../types'; - -// Mock VS Code API -jest.mock('vscode', () => ({ - workspace: { - workspaceFolders: [{ - uri: { fsPath: '/test/workspace' } - }] - }, - window: { - showInputBox: jest.fn(), - showQuickPick: jest.fn(), - showInformationMessage: jest.fn(), - showErrorMessage: jest.fn() - } -})); - -// Mock helpers -jest.mock('../../helpers', () => ({ - showMessage: jest.fn(), - showSearchDropdown: jest.fn(), - createFolder: jest.fn(), - createFile: jest.fn(), - checkExistsFile: jest.fn(), - formatTemplateName: jest.fn((templateFile, templateName, componentName) => { - return templateFile.replace('.hbs', '').replace(templateName, componentName); - }) -})); - -describe('updateComponent with hierarchical support', () => { - let tempDir: string; - let fileSystemManager: FileSystemManager; - let mockHelpers: any; - - beforeEach(() => { - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codebot-update-test-')); - fileSystemManager = new FileSystemManager(); - - // Get mocked helpers - mockHelpers = require('../../helpers'); - - // Reset all mocks - jest.clearAllMocks(); - }); - - afterEach(() => { - if (fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true, force: true }); - } - }); - - describe('hierarchical template processing', () => { - beforeEach(async () => { - // Create a complex template structure - const templateStructure = { - 'component.hbs': 'export const {{name}} = () => {};', - 'component.test.hbs': 'describe("{{name}}", () => {});', - 'styles/component.css.hbs': '.{{name}} { color: red; }', - 'styles/themes/light.css.hbs': '.{{name}}.light { background: white; }', - 'utils/helper.js.hbs': 'export const {{name}}Helper = () => {};' - }; - - const templatesDir = path.join(tempDir, 'templates', 'react-component'); - await fileSystemManager.createFolderRecursive(templatesDir); - - for (const [filePath, content] of Object.entries(templateStructure)) { - const fullPath = path.join(templatesDir, filePath); - await fileSystemManager.createFile(fullPath, content); - } - - // Create component directory - const componentDir = path.join(tempDir, 'UserProfile'); - await fileSystemManager.createFolderRecursive(componentDir); - }); - - it('should create missing files in hierarchical structure', async () => { - const componentDir = path.join(tempDir, 'UserProfile'); - - // Mock helpers to simulate user interactions - mockHelpers.showSearchDropdown.mockResolvedValue('react-component'); - mockHelpers.checkExistsFile.mockImplementation((filePath: string) => { - return fs.existsSync(filePath); - }); - mockHelpers.createFolder.mockImplementation((folderPath: string) => { - fs.mkdirSync(folderPath, { recursive: true }); - }); - mockHelpers.createFile.mockImplementation((filePath: string, content: string) => { - fs.mkdirSync(path.dirname(filePath), { recursive: true }); - fs.writeFileSync(filePath, content); - }); - - const args: CommandArgs = { fsPath: componentDir }; - - await updateComponent(args); - - // Verify error message was shown (no templates found) - expect(mockHelpers.showMessage).toHaveBeenCalledWith( - expect.stringContaining('No templates found in project'), - 'error' - ); - }); - - it('should skip existing files and only create missing ones', async () => { - const componentDir = path.join(tempDir, 'UserProfile'); - - // Pre-create some files to simulate existing component - const existingFiles = [ - 'UserProfile.js', // component.hbs -> UserProfile.js - 'styles/UserProfile.css' // styles/component.css.hbs -> styles/UserProfile.css - ]; - - for (const file of existingFiles) { - const filePath = path.join(componentDir, file); - await fileSystemManager.createFile(filePath, 'existing content'); - } - - // Mock helpers - mockHelpers.showSearchDropdown.mockResolvedValue('react-component'); - mockHelpers.checkExistsFile.mockImplementation((filePath: string) => { - return fs.existsSync(filePath); - }); - mockHelpers.createFolder.mockImplementation((folderPath: string) => { - if (!fs.existsSync(folderPath)) { - fs.mkdirSync(folderPath, { recursive: true }); - } - }); - mockHelpers.createFile.mockImplementation((filePath: string, content: string) => { - if (!fs.existsSync(filePath)) { - fs.mkdirSync(path.dirname(filePath), { recursive: true }); - fs.writeFileSync(filePath, content); - } - }); - - const args: CommandArgs = { fsPath: componentDir }; - - await updateComponent(args); - - // Verify error message was shown (no templates found) - expect(mockHelpers.showMessage).toHaveBeenCalledWith( - expect.stringContaining('No templates found in project'), - 'error' - ); - }); - - it('should handle templates with only subdirectories', async () => { - // Create a template with only subdirectory files - const templateDir = path.join(tempDir, 'templates', 'styles-only'); - const templateStructure = { - 'styles/main.css.hbs': '.{{name}} { display: block; }', - 'styles/themes/dark.css.hbs': '.{{name}}.dark { color: white; }', - 'assets/icons/icon.svg.hbs': '{{name}}' - }; - - await fileSystemManager.createFolderRecursive(templateDir); - for (const [filePath, content] of Object.entries(templateStructure)) { - const fullPath = path.join(templateDir, filePath); - await fileSystemManager.createFile(fullPath, content); - } - - const componentDir = path.join(tempDir, 'StyleComponent'); - await fileSystemManager.createFolderRecursive(componentDir); - - // Mock helpers - mockHelpers.showSearchDropdown.mockResolvedValue('styles-only'); - mockHelpers.checkExistsFile.mockImplementation((filePath: string) => { - return fs.existsSync(filePath); - }); - mockHelpers.createFolder.mockImplementation((folderPath: string) => { - fs.mkdirSync(folderPath, { recursive: true }); - }); - mockHelpers.createFile.mockImplementation((filePath: string, content: string) => { - fs.mkdirSync(path.dirname(filePath), { recursive: true }); - fs.writeFileSync(filePath, content); - }); - - const args: CommandArgs = { fsPath: componentDir }; - - await updateComponent(args); - - // Should show error message (no templates found) - expect(mockHelpers.showMessage).toHaveBeenCalledWith( - expect.stringContaining('No templates found in project'), - 'error' - ); - }); - - it('should report when component is already up to date', async () => { - const componentDir = path.join(tempDir, 'ExistingComponent'); - - // Pre-create all template files - const allFiles = [ - 'ExistingComponent.js', - 'ExistingComponent.test.js', - 'styles/ExistingComponent.css', - 'styles/themes/light.css', - 'utils/helper.js' - ]; - - for (const file of allFiles) { - const filePath = path.join(componentDir, file); - await fileSystemManager.createFile(filePath, 'existing content'); - } - - // Mock helpers - mockHelpers.showSearchDropdown.mockResolvedValue('react-component'); - mockHelpers.checkExistsFile.mockImplementation((filePath: string) => { - return fs.existsSync(filePath); - }); - - const args: CommandArgs = { fsPath: componentDir }; - - await updateComponent(args); - - // Should show error message (no templates found) - expect(mockHelpers.showMessage).toHaveBeenCalledWith( - expect.stringContaining('No templates found in project'), - 'error' - ); - }); - }); - - describe('error handling', () => { - it('should handle empty template structures', async () => { - // Create empty template directory - const emptyTemplateDir = path.join(tempDir, 'templates', 'empty-template'); - await fileSystemManager.createFolderRecursive(emptyTemplateDir); - - const componentDir = path.join(tempDir, 'TestComponent'); - await fileSystemManager.createFolderRecursive(componentDir); - - mockHelpers.showSearchDropdown.mockResolvedValue('empty-template'); - mockHelpers.checkExistsFile.mockImplementation((filePath: string) => { - return fs.existsSync(filePath); - }); - - const args: CommandArgs = { fsPath: componentDir }; - - await updateComponent(args); - - // Should show error for no templates found - expect(mockHelpers.showMessage).toHaveBeenCalledWith( - expect.stringContaining('No templates found in project'), - 'error' - ); - }); - - it('should handle file creation errors gracefully', async () => { - const componentDir = path.join(tempDir, 'ErrorComponent'); - await fileSystemManager.createFolderRecursive(componentDir); - - // Mock helpers - mockHelpers.showSearchDropdown.mockResolvedValue('react-component'); - mockHelpers.checkExistsFile.mockReturnValue(false); // No files exist - mockHelpers.createFile.mockImplementation(() => { - throw new Error('Permission denied'); - }); - - const args: CommandArgs = { fsPath: componentDir }; - - await updateComponent(args); - - // Should show error message (no templates found) - expect(mockHelpers.showMessage).toHaveBeenCalledWith( - expect.stringContaining('No templates found in project'), - 'error' - ); - }); - }); -}); \ No newline at end of file +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import { updateComponent } from '../../commands/updateComponent'; +import { FileSystemManager } from '../../managers/FileSystemManager'; +import type { CommandArgs } from '../../types'; + +// Mock VS Code API +jest.mock('vscode', () => ({ + workspace: { + workspaceFolders: [ + { + uri: { fsPath: '/test/workspace' }, + }, + ], + }, + window: { + showInputBox: jest.fn(), + showQuickPick: jest.fn(), + showInformationMessage: jest.fn(), + showErrorMessage: jest.fn(), + }, +})); + +// Mock helpers +jest.mock('../../helpers', () => ({ + showMessage: jest.fn(), + showSearchDropdown: jest.fn(), + createFolder: jest.fn(), + createFile: jest.fn(), + checkExistsFile: jest.fn(), + formatTemplateName: jest.fn((templateFile, templateName, componentName) => { + return templateFile + .replace('.hbs', '') + .replace(templateName, componentName); + }), +})); + +describe('updateComponent with hierarchical support', () => { + let tempDir: string; + let fileSystemManager: FileSystemManager; + let mockHelpers: jest.Mocked; + + beforeEach(() => { + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codebot-update-test-')); + fileSystemManager = new FileSystemManager(); + + // Get mocked helpers + mockHelpers = require('../../helpers'); + + // Reset all mocks + jest.clearAllMocks(); + }); + + afterEach(() => { + if (fs.existsSync(tempDir)) { + fs.rmSync(tempDir, { recursive: true, force: true }); + } + }); + + describe('hierarchical template processing', () => { + beforeEach(async () => { + // Create a complex template structure + const templateStructure = { + 'component.hbs': 'export const {{name}} = () => {};', + 'component.test.hbs': 'describe("{{name}}", () => {});', + 'styles/component.css.hbs': '.{{name}} { color: red; }', + 'styles/themes/light.css.hbs': '.{{name}}.light { background: white; }', + 'utils/helper.js.hbs': 'export const {{name}}Helper = () => {};', + }; + + const templatesDir = path.join(tempDir, 'templates', 'react-component'); + await fileSystemManager.createFolderRecursive(templatesDir); + + for (const [filePath, content] of Object.entries(templateStructure)) { + const fullPath = path.join(templatesDir, filePath); + await fileSystemManager.createFile(fullPath, content); + } + + // Create component directory + const componentDir = path.join(tempDir, 'UserProfile'); + await fileSystemManager.createFolderRecursive(componentDir); + }); + + it('should create missing files in hierarchical structure', async () => { + const componentDir = path.join(tempDir, 'UserProfile'); + + // Mock helpers to simulate user interactions + mockHelpers.showSearchDropdown.mockResolvedValue('react-component'); + mockHelpers.checkExistsFile.mockImplementation((filePath: string) => { + return fs.existsSync(filePath); + }); + mockHelpers.createFolder.mockImplementation((folderPath: string) => { + fs.mkdirSync(folderPath, { recursive: true }); + }); + mockHelpers.createFile.mockImplementation( + (filePath: string, content: string) => { + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, content); + }, + ); + + const args: CommandArgs = { fsPath: componentDir }; + + await updateComponent(args); + + // Verify error message was shown (no templates found) + expect(mockHelpers.showMessage).toHaveBeenCalledWith( + expect.stringContaining('No templates found in project'), + 'error', + ); + }); + + it('should skip existing files and only create missing ones', async () => { + const componentDir = path.join(tempDir, 'UserProfile'); + + // Pre-create some files to simulate existing component + const existingFiles = [ + 'UserProfile.js', // component.hbs -> UserProfile.js + 'styles/UserProfile.css', // styles/component.css.hbs -> styles/UserProfile.css + ]; + + for (const file of existingFiles) { + const filePath = path.join(componentDir, file); + await fileSystemManager.createFile(filePath, 'existing content'); + } + + // Mock helpers + mockHelpers.showSearchDropdown.mockResolvedValue('react-component'); + mockHelpers.checkExistsFile.mockImplementation((filePath: string) => { + return fs.existsSync(filePath); + }); + mockHelpers.createFolder.mockImplementation((folderPath: string) => { + if (!fs.existsSync(folderPath)) { + fs.mkdirSync(folderPath, { recursive: true }); + } + }); + mockHelpers.createFile.mockImplementation( + (filePath: string, content: string) => { + if (!fs.existsSync(filePath)) { + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, content); + } + }, + ); + + const args: CommandArgs = { fsPath: componentDir }; + + await updateComponent(args); + + // Verify error message was shown (no templates found) + expect(mockHelpers.showMessage).toHaveBeenCalledWith( + expect.stringContaining('No templates found in project'), + 'error', + ); + }); + + it('should handle templates with only subdirectories', async () => { + // Create a template with only subdirectory files + const templateDir = path.join(tempDir, 'templates', 'styles-only'); + const templateStructure = { + 'styles/main.css.hbs': '.{{name}} { display: block; }', + 'styles/themes/dark.css.hbs': '.{{name}}.dark { color: white; }', + 'assets/icons/icon.svg.hbs': '{{name}}', + }; + + await fileSystemManager.createFolderRecursive(templateDir); + for (const [filePath, content] of Object.entries(templateStructure)) { + const fullPath = path.join(templateDir, filePath); + await fileSystemManager.createFile(fullPath, content); + } + + const componentDir = path.join(tempDir, 'StyleComponent'); + await fileSystemManager.createFolderRecursive(componentDir); + + // Mock helpers + mockHelpers.showSearchDropdown.mockResolvedValue('styles-only'); + mockHelpers.checkExistsFile.mockImplementation((filePath: string) => { + return fs.existsSync(filePath); + }); + mockHelpers.createFolder.mockImplementation((folderPath: string) => { + fs.mkdirSync(folderPath, { recursive: true }); + }); + mockHelpers.createFile.mockImplementation( + (filePath: string, content: string) => { + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, content); + }, + ); + + const args: CommandArgs = { fsPath: componentDir }; + + await updateComponent(args); + + // Should show error message (no templates found) + expect(mockHelpers.showMessage).toHaveBeenCalledWith( + expect.stringContaining('No templates found in project'), + 'error', + ); + }); + + it('should report when component is already up to date', async () => { + const componentDir = path.join(tempDir, 'ExistingComponent'); + + // Pre-create all template files + const allFiles = [ + 'ExistingComponent.js', + 'ExistingComponent.test.js', + 'styles/ExistingComponent.css', + 'styles/themes/light.css', + 'utils/helper.js', + ]; + + for (const file of allFiles) { + const filePath = path.join(componentDir, file); + await fileSystemManager.createFile(filePath, 'existing content'); + } + + // Mock helpers + mockHelpers.showSearchDropdown.mockResolvedValue('react-component'); + mockHelpers.checkExistsFile.mockImplementation((filePath: string) => { + return fs.existsSync(filePath); + }); + + const args: CommandArgs = { fsPath: componentDir }; + + await updateComponent(args); + + // Should show error message (no templates found) + expect(mockHelpers.showMessage).toHaveBeenCalledWith( + expect.stringContaining('No templates found in project'), + 'error', + ); + }); + }); + + describe('error handling', () => { + it('should handle empty template structures', async () => { + // Create empty template directory + const emptyTemplateDir = path.join( + tempDir, + 'templates', + 'empty-template', + ); + await fileSystemManager.createFolderRecursive(emptyTemplateDir); + + const componentDir = path.join(tempDir, 'TestComponent'); + await fileSystemManager.createFolderRecursive(componentDir); + + mockHelpers.showSearchDropdown.mockResolvedValue('empty-template'); + mockHelpers.checkExistsFile.mockImplementation((filePath: string) => { + return fs.existsSync(filePath); + }); + + const args: CommandArgs = { fsPath: componentDir }; + + await updateComponent(args); + + // Should show error for no templates found + expect(mockHelpers.showMessage).toHaveBeenCalledWith( + expect.stringContaining('No templates found in project'), + 'error', + ); + }); + + it('should handle file creation errors gracefully', async () => { + const componentDir = path.join(tempDir, 'ErrorComponent'); + await fileSystemManager.createFolderRecursive(componentDir); + + // Mock helpers + mockHelpers.showSearchDropdown.mockResolvedValue('react-component'); + mockHelpers.checkExistsFile.mockReturnValue(false); // No files exist + mockHelpers.createFile.mockImplementation(() => { + throw new Error('Permission denied'); + }); + + const args: CommandArgs = { fsPath: componentDir }; + + await updateComponent(args); + + // Should show error message (no templates found) + expect(mockHelpers.showMessage).toHaveBeenCalledWith( + expect.stringContaining('No templates found in project'), + 'error', + ); + }); + }); +}); diff --git a/src/test/fixtures/index.ts b/src/test/fixtures/index.ts index 6cbe70f..0318d4e 100644 --- a/src/test/fixtures/index.ts +++ b/src/test/fixtures/index.ts @@ -1,57 +1,61 @@ -import { ConfigurationFile, ProjectContext, TemplateType } from '../../types'; - -export const mockConfiguration: ConfigurationFile = { - templateFolderPath: 'templates', - multiProject: { - enabled: true, - projectDetection: 'auto' - } -}; - -export const mockProjectContext: ProjectContext = { - workspaceRoot: '/workspace', - projectRoot: '/workspace/project1', - templatePath: '/workspace/project1/templates', - configPath: '/workspace/project1/codebot.config.json', - isMultiProject: true, - projectName: 'project1' -}; - -export const mockTemplateType: TemplateType = { - name: 'ComponentSass', - path: '/workspace/templates/ComponentSass', - files: ['Component.tsx.hbs', 'Component.module.scss.hbs', 'index.tsx.hbs'], - hasSubdirectories: false, - structure: { - rootPath: '/workspace/templates/ComponentSass', - files: [ - { - name: 'Component.tsx.hbs', - path: '/workspace/templates/ComponentSass/Component.tsx.hbs', - relativePath: 'Component.tsx.hbs', - extension: '.hbs' - }, - { - name: 'Component.module.scss.hbs', - path: '/workspace/templates/ComponentSass/Component.module.scss.hbs', - relativePath: 'Component.module.scss.hbs', - extension: '.hbs' - }, - { - name: 'index.tsx.hbs', - path: '/workspace/templates/ComponentSass/index.tsx.hbs', - relativePath: 'index.tsx.hbs', - extension: '.hbs' - } - ], - directories: [] - } -}; - -export const mockSingleProjectContext: ProjectContext = { - workspaceRoot: '/workspace', - projectRoot: '/workspace', - templatePath: '/workspace/templates', - configPath: '/workspace/codebot.config.json', - isMultiProject: false -}; \ No newline at end of file +import type { + ConfigurationFile, + ProjectContext, + TemplateType, +} from '../../types'; + +export const mockConfiguration: ConfigurationFile = { + templateFolderPath: 'templates', + multiProject: { + enabled: true, + projectDetection: 'auto', + }, +}; + +export const mockProjectContext: ProjectContext = { + workspaceRoot: '/workspace', + projectRoot: '/workspace/project1', + templatePath: '/workspace/project1/templates', + configPath: '/workspace/project1/codebot.config.json', + isMultiProject: true, + projectName: 'project1', +}; + +export const mockTemplateType: TemplateType = { + name: 'ComponentSass', + path: '/workspace/templates/ComponentSass', + files: ['Component.tsx.hbs', 'Component.module.scss.hbs', 'index.tsx.hbs'], + hasSubdirectories: false, + structure: { + rootPath: '/workspace/templates/ComponentSass', + files: [ + { + name: 'Component.tsx.hbs', + path: '/workspace/templates/ComponentSass/Component.tsx.hbs', + relativePath: 'Component.tsx.hbs', + extension: '.hbs', + }, + { + name: 'Component.module.scss.hbs', + path: '/workspace/templates/ComponentSass/Component.module.scss.hbs', + relativePath: 'Component.module.scss.hbs', + extension: '.hbs', + }, + { + name: 'index.tsx.hbs', + path: '/workspace/templates/ComponentSass/index.tsx.hbs', + relativePath: 'index.tsx.hbs', + extension: '.hbs', + }, + ], + directories: [], + }, +}; + +export const mockSingleProjectContext: ProjectContext = { + workspaceRoot: '/workspace', + projectRoot: '/workspace', + templatePath: '/workspace/templates', + configPath: '/workspace/codebot.config.json', + isMultiProject: false, +}; diff --git a/src/test/managers/FileSystemManager.test.ts b/src/test/managers/FileSystemManager.test.ts index db56d15..a260178 100644 --- a/src/test/managers/FileSystemManager.test.ts +++ b/src/test/managers/FileSystemManager.test.ts @@ -1,351 +1,375 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import * as os from 'os'; -import { FileSystemManager } from '../../managers/FileSystemManager'; -import { FileSystemEntry, ErrorType } from '../../types'; - -describe('FileSystemManager', () => { - let fileSystemManager: FileSystemManager; - let tempDir: string; - - beforeEach(() => { - fileSystemManager = new FileSystemManager(); - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codebot-test-')); - }); - - afterEach(() => { - // Clean up temp directory - if (fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true, force: true }); - } - }); - - describe('createFile', () => { - it('should create a file with content', async () => { - const filePath = path.join(tempDir, 'test.txt'); - const content = 'Hello, World!'; - - await fileSystemManager.createFile(filePath, content); - - expect(fs.existsSync(filePath)).toBe(true); - expect(fs.readFileSync(filePath, 'utf8')).toBe(content); - }); - - it('should create parent directories if they do not exist', async () => { - const filePath = path.join(tempDir, 'nested', 'deep', 'test.txt'); - const content = 'Nested file content'; - - await fileSystemManager.createFile(filePath, content); - - expect(fs.existsSync(filePath)).toBe(true); - expect(fs.readFileSync(filePath, 'utf8')).toBe(content); - }); - }); - - describe('createFolder', () => { - it('should create a folder', async () => { - const folderPath = path.join(tempDir, 'testFolder'); - - await fileSystemManager.createFolder(folderPath); - - expect(fs.existsSync(folderPath)).toBe(true); - expect(fs.statSync(folderPath).isDirectory()).toBe(true); - }); - - it('should not throw error if folder already exists', async () => { - const folderPath = path.join(tempDir, 'existingFolder'); - fs.mkdirSync(folderPath); - - await expect(fileSystemManager.createFolder(folderPath)).resolves.not.toThrow(); - }); - }); - - describe('createFolderRecursive', () => { - it('should create nested folder structure', async () => { - const folderPath = path.join(tempDir, 'level1', 'level2', 'level3'); - - await fileSystemManager.createFolderRecursive(folderPath); - - expect(fs.existsSync(folderPath)).toBe(true); - expect(fs.statSync(folderPath).isDirectory()).toBe(true); - }); - - it('should not throw error if folder structure already exists', async () => { - const folderPath = path.join(tempDir, 'existing', 'nested'); - fs.mkdirSync(folderPath, { recursive: true }); - - await expect(fileSystemManager.createFolderRecursive(folderPath)).resolves.not.toThrow(); - }); - }); - - describe('fileExists', () => { - it('should return true for existing file', async () => { - const filePath = path.join(tempDir, 'existing.txt'); - fs.writeFileSync(filePath, 'content'); - - const exists = await fileSystemManager.fileExists(filePath); - - expect(exists).toBe(true); - }); - - it('should return false for non-existing file', async () => { - const filePath = path.join(tempDir, 'nonexistent.txt'); - - const exists = await fileSystemManager.fileExists(filePath); - - expect(exists).toBe(false); - }); - - it('should return false for directory path', async () => { - const dirPath = path.join(tempDir, 'directory'); - fs.mkdirSync(dirPath); - - const exists = await fileSystemManager.fileExists(dirPath); - - expect(exists).toBe(false); - }); - }); - - describe('folderExists', () => { - it('should return true for existing folder', async () => { - const folderPath = path.join(tempDir, 'existingFolder'); - fs.mkdirSync(folderPath); - - const exists = await fileSystemManager.folderExists(folderPath); - - expect(exists).toBe(true); - }); - - it('should return false for non-existing folder', async () => { - const folderPath = path.join(tempDir, 'nonexistentFolder'); - - const exists = await fileSystemManager.folderExists(folderPath); - - expect(exists).toBe(false); - }); - - it('should return false for file path', async () => { - const filePath = path.join(tempDir, 'file.txt'); - fs.writeFileSync(filePath, 'content'); - - const exists = await fileSystemManager.folderExists(filePath); - - expect(exists).toBe(false); - }); - }); - - describe('readFile', () => { - it('should read file content', async () => { - const filePath = path.join(tempDir, 'content.txt'); - const content = 'File content to read'; - fs.writeFileSync(filePath, content); - - const readContent = await fileSystemManager.readFile(filePath); - - expect(readContent).toBe(content); - }); - - it('should throw error for non-existing file', async () => { - const filePath = path.join(tempDir, 'nonexistent.txt'); - - await expect(fileSystemManager.readFile(filePath)).rejects.toMatchObject({ - type: ErrorType.FILE_SYSTEM_ERROR - }); - }); - }); - - describe('listFiles', () => { - it('should list only files in directory', async () => { - const file1 = path.join(tempDir, 'file1.txt'); - const file2 = path.join(tempDir, 'file2.js'); - const subDir = path.join(tempDir, 'subdir'); - - fs.writeFileSync(file1, 'content1'); - fs.writeFileSync(file2, 'content2'); - fs.mkdirSync(subDir); - - const files = await fileSystemManager.listFiles(tempDir); - - expect(files).toHaveLength(2); - expect(files).toContain('file1.txt'); - expect(files).toContain('file2.js'); - expect(files).not.toContain('subdir'); - }); - - it('should return empty array for non-existing directory', async () => { - const nonExistentDir = path.join(tempDir, 'nonexistent'); - - const files = await fileSystemManager.listFiles(nonExistentDir); - - expect(files).toEqual([]); - }); - }); - - describe('listDirectories', () => { - it('should list only directories in directory', async () => { - const file1 = path.join(tempDir, 'file1.txt'); - const file2 = path.join(tempDir, 'file2.js'); - const subDir1 = path.join(tempDir, 'subdir1'); - const subDir2 = path.join(tempDir, 'subdir2'); - - fs.writeFileSync(file1, 'content1'); - fs.writeFileSync(file2, 'content2'); - fs.mkdirSync(subDir1); - fs.mkdirSync(subDir2); - - const directories = await fileSystemManager.listDirectories(tempDir); - - expect(directories).toHaveLength(2); - expect(directories).toContain('subdir1'); - expect(directories).toContain('subdir2'); - expect(directories).not.toContain('file1.txt'); - expect(directories).not.toContain('file2.js'); - }); - - it('should return empty array for non-existing directory', async () => { - const nonExistentDir = path.join(tempDir, 'nonexistent'); - - const directories = await fileSystemManager.listDirectories(nonExistentDir); - - expect(directories).toEqual([]); - }); - }); - - describe('listFilesRecursive', () => { - beforeEach(() => { - // Create test directory structure - const structure = { - 'file1.txt': 'content1', - 'file2.js': 'content2', - 'subdir1/file3.ts': 'content3', - 'subdir1/file4.css': 'content4', - 'subdir1/nested/file5.html': 'content5', - 'subdir2/file6.json': 'content6' - }; - - Object.entries(structure).forEach(([filePath, content]) => { - const fullPath = path.join(tempDir, filePath); - fs.mkdirSync(path.dirname(fullPath), { recursive: true }); - fs.writeFileSync(fullPath, content); - }); - }); - - it('should list all files and directories recursively', async () => { - const entries = await fileSystemManager.listFilesRecursive(tempDir); - - // Should have root files and directories - expect(entries.length).toBeGreaterThan(0); - - // Check for root files - const rootFiles = entries.filter(e => !e.isDirectory); - expect(rootFiles.some(f => f.name === 'file1.txt')).toBe(true); - expect(rootFiles.some(f => f.name === 'file2.js')).toBe(true); - - // Check for directories - const directories = entries.filter(e => e.isDirectory); - expect(directories.some(d => d.name === 'subdir1')).toBe(true); - expect(directories.some(d => d.name === 'subdir2')).toBe(true); - - // Check nested structure - const subdir1 = directories.find(d => d.name === 'subdir1'); - expect(subdir1?.children).toBeDefined(); - expect(subdir1?.children?.some(c => c.name === 'file3.ts')).toBe(true); - expect(subdir1?.children?.some(c => c.name === 'nested' && c.isDirectory)).toBe(true); - }); - - it('should set correct relative paths', async () => { - const entries = await fileSystemManager.listFilesRecursive(tempDir); - - const subdir1 = entries.find(e => e.name === 'subdir1' && e.isDirectory); - expect(subdir1?.relativePath).toBe('subdir1'); - - const nestedFile = subdir1?.children?.find(c => c.name === 'file3.ts'); - expect(nestedFile?.relativePath).toBe('file3.ts'); - }); - - it('should return empty array for non-existing directory', async () => { - const nonExistentDir = path.join(tempDir, 'nonexistent'); - - const entries = await fileSystemManager.listFilesRecursive(nonExistentDir); - - expect(entries).toEqual([]); - }); - - it('should throw error when maximum depth is exceeded', async () => { - // Create a very deep directory structure (more than 15 levels) - let deepPath = tempDir; - for (let i = 0; i < 17; i++) { - deepPath = path.join(deepPath, `level${i}`); - fs.mkdirSync(deepPath, { recursive: true }); - } - - await expect(fileSystemManager.listFilesRecursive(tempDir)).rejects.toMatchObject({ - type: ErrorType.FILE_SYSTEM_ERROR, - message: expect.stringContaining('Maximum directory depth exceeded') - }); - }); - }); - - describe('copyDirectoryStructure', () => { - let sourceDir: string; - let targetDir: string; - - beforeEach(() => { - sourceDir = path.join(tempDir, 'source'); - targetDir = path.join(tempDir, 'target'); - - // Create source directory structure - const structure = { - 'file1.txt': 'content1', - 'file2.js': 'content2', - 'subdir1/file3.ts': 'content3', - 'subdir1/nested/file4.html': 'content4', - 'subdir2/file5.json': 'content5' - }; - - fs.mkdirSync(sourceDir); - Object.entries(structure).forEach(([filePath, content]) => { - const fullPath = path.join(sourceDir, filePath); - fs.mkdirSync(path.dirname(fullPath), { recursive: true }); - fs.writeFileSync(fullPath, content); - }); - }); - - it('should copy entire directory structure', async () => { - await fileSystemManager.copyDirectoryStructure(sourceDir, targetDir); - - // Verify target directory exists - expect(fs.existsSync(targetDir)).toBe(true); - - // Verify files are copied - expect(fs.existsSync(path.join(targetDir, 'file1.txt'))).toBe(true); - expect(fs.existsSync(path.join(targetDir, 'file2.js'))).toBe(true); - expect(fs.existsSync(path.join(targetDir, 'subdir1', 'file3.ts'))).toBe(true); - expect(fs.existsSync(path.join(targetDir, 'subdir1', 'nested', 'file4.html'))).toBe(true); - expect(fs.existsSync(path.join(targetDir, 'subdir2', 'file5.json'))).toBe(true); - - // Verify content is copied correctly - expect(fs.readFileSync(path.join(targetDir, 'file1.txt'), 'utf8')).toBe('content1'); - expect(fs.readFileSync(path.join(targetDir, 'subdir1', 'nested', 'file4.html'), 'utf8')).toBe('content4'); - }); - - it('should create target directory if it does not exist', async () => { - const deepTargetDir = path.join(tempDir, 'deep', 'nested', 'target'); - - await fileSystemManager.copyDirectoryStructure(sourceDir, deepTargetDir); - - expect(fs.existsSync(deepTargetDir)).toBe(true); - expect(fs.existsSync(path.join(deepTargetDir, 'file1.txt'))).toBe(true); - }); - - it('should throw error if source directory does not exist', async () => { - const nonExistentSource = path.join(tempDir, 'nonexistent'); - - await expect(fileSystemManager.copyDirectoryStructure(nonExistentSource, targetDir)) - .rejects.toMatchObject({ - type: ErrorType.FILE_SYSTEM_ERROR, - message: expect.stringContaining('Source directory does not exist') - }); - }); - }); -}); \ No newline at end of file +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import { FileSystemManager } from '../../managers/FileSystemManager'; +import { ErrorType } from '../../types'; + +describe('FileSystemManager', () => { + let fileSystemManager: FileSystemManager; + let tempDir: string; + + beforeEach(() => { + fileSystemManager = new FileSystemManager(); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codebot-test-')); + }); + + afterEach(() => { + // Clean up temp directory + if (fs.existsSync(tempDir)) { + fs.rmSync(tempDir, { recursive: true, force: true }); + } + }); + + describe('createFile', () => { + it('should create a file with content', async () => { + const filePath = path.join(tempDir, 'test.txt'); + const content = 'Hello, World!'; + + await fileSystemManager.createFile(filePath, content); + + expect(fs.existsSync(filePath)).toBe(true); + expect(fs.readFileSync(filePath, 'utf8')).toBe(content); + }); + + it('should create parent directories if they do not exist', async () => { + const filePath = path.join(tempDir, 'nested', 'deep', 'test.txt'); + const content = 'Nested file content'; + + await fileSystemManager.createFile(filePath, content); + + expect(fs.existsSync(filePath)).toBe(true); + expect(fs.readFileSync(filePath, 'utf8')).toBe(content); + }); + }); + + describe('createFolder', () => { + it('should create a folder', async () => { + const folderPath = path.join(tempDir, 'testFolder'); + + await fileSystemManager.createFolder(folderPath); + + expect(fs.existsSync(folderPath)).toBe(true); + expect(fs.statSync(folderPath).isDirectory()).toBe(true); + }); + + it('should not throw error if folder already exists', async () => { + const folderPath = path.join(tempDir, 'existingFolder'); + fs.mkdirSync(folderPath); + + await expect( + fileSystemManager.createFolder(folderPath), + ).resolves.not.toThrow(); + }); + }); + + describe('createFolderRecursive', () => { + it('should create nested folder structure', async () => { + const folderPath = path.join(tempDir, 'level1', 'level2', 'level3'); + + await fileSystemManager.createFolderRecursive(folderPath); + + expect(fs.existsSync(folderPath)).toBe(true); + expect(fs.statSync(folderPath).isDirectory()).toBe(true); + }); + + it('should not throw error if folder structure already exists', async () => { + const folderPath = path.join(tempDir, 'existing', 'nested'); + fs.mkdirSync(folderPath, { recursive: true }); + + await expect( + fileSystemManager.createFolderRecursive(folderPath), + ).resolves.not.toThrow(); + }); + }); + + describe('fileExists', () => { + it('should return true for existing file', async () => { + const filePath = path.join(tempDir, 'existing.txt'); + fs.writeFileSync(filePath, 'content'); + + const exists = await fileSystemManager.fileExists(filePath); + + expect(exists).toBe(true); + }); + + it('should return false for non-existing file', async () => { + const filePath = path.join(tempDir, 'nonexistent.txt'); + + const exists = await fileSystemManager.fileExists(filePath); + + expect(exists).toBe(false); + }); + + it('should return false for directory path', async () => { + const dirPath = path.join(tempDir, 'directory'); + fs.mkdirSync(dirPath); + + const exists = await fileSystemManager.fileExists(dirPath); + + expect(exists).toBe(false); + }); + }); + + describe('folderExists', () => { + it('should return true for existing folder', async () => { + const folderPath = path.join(tempDir, 'existingFolder'); + fs.mkdirSync(folderPath); + + const exists = await fileSystemManager.folderExists(folderPath); + + expect(exists).toBe(true); + }); + + it('should return false for non-existing folder', async () => { + const folderPath = path.join(tempDir, 'nonexistentFolder'); + + const exists = await fileSystemManager.folderExists(folderPath); + + expect(exists).toBe(false); + }); + + it('should return false for file path', async () => { + const filePath = path.join(tempDir, 'file.txt'); + fs.writeFileSync(filePath, 'content'); + + const exists = await fileSystemManager.folderExists(filePath); + + expect(exists).toBe(false); + }); + }); + + describe('readFile', () => { + it('should read file content', async () => { + const filePath = path.join(tempDir, 'content.txt'); + const content = 'File content to read'; + fs.writeFileSync(filePath, content); + + const readContent = await fileSystemManager.readFile(filePath); + + expect(readContent).toBe(content); + }); + + it('should throw error for non-existing file', async () => { + const filePath = path.join(tempDir, 'nonexistent.txt'); + + await expect(fileSystemManager.readFile(filePath)).rejects.toMatchObject({ + type: ErrorType.FILE_SYSTEM_ERROR, + }); + }); + }); + + describe('listFiles', () => { + it('should list only files in directory', async () => { + const file1 = path.join(tempDir, 'file1.txt'); + const file2 = path.join(tempDir, 'file2.js'); + const subDir = path.join(tempDir, 'subdir'); + + fs.writeFileSync(file1, 'content1'); + fs.writeFileSync(file2, 'content2'); + fs.mkdirSync(subDir); + + const files = await fileSystemManager.listFiles(tempDir); + + expect(files).toHaveLength(2); + expect(files).toContain('file1.txt'); + expect(files).toContain('file2.js'); + expect(files).not.toContain('subdir'); + }); + + it('should return empty array for non-existing directory', async () => { + const nonExistentDir = path.join(tempDir, 'nonexistent'); + + const files = await fileSystemManager.listFiles(nonExistentDir); + + expect(files).toEqual([]); + }); + }); + + describe('listDirectories', () => { + it('should list only directories in directory', async () => { + const file1 = path.join(tempDir, 'file1.txt'); + const file2 = path.join(tempDir, 'file2.js'); + const subDir1 = path.join(tempDir, 'subdir1'); + const subDir2 = path.join(tempDir, 'subdir2'); + + fs.writeFileSync(file1, 'content1'); + fs.writeFileSync(file2, 'content2'); + fs.mkdirSync(subDir1); + fs.mkdirSync(subDir2); + + const directories = await fileSystemManager.listDirectories(tempDir); + + expect(directories).toHaveLength(2); + expect(directories).toContain('subdir1'); + expect(directories).toContain('subdir2'); + expect(directories).not.toContain('file1.txt'); + expect(directories).not.toContain('file2.js'); + }); + + it('should return empty array for non-existing directory', async () => { + const nonExistentDir = path.join(tempDir, 'nonexistent'); + + const directories = + await fileSystemManager.listDirectories(nonExistentDir); + + expect(directories).toEqual([]); + }); + }); + + describe('listFilesRecursive', () => { + beforeEach(() => { + // Create test directory structure + const structure = { + 'file1.txt': 'content1', + 'file2.js': 'content2', + 'subdir1/file3.ts': 'content3', + 'subdir1/file4.css': 'content4', + 'subdir1/nested/file5.html': 'content5', + 'subdir2/file6.json': 'content6', + }; + + for (const [filePath, content] of Object.entries(structure)) { + const fullPath = path.join(tempDir, filePath); + fs.mkdirSync(path.dirname(fullPath), { recursive: true }); + fs.writeFileSync(fullPath, content); + } + }); + + it('should list all files and directories recursively', async () => { + const entries = await fileSystemManager.listFilesRecursive(tempDir); + + // Should have root files and directories + expect(entries.length).toBeGreaterThan(0); + + // Check for root files + const rootFiles = entries.filter(e => !e.isDirectory); + expect(rootFiles.some(f => f.name === 'file1.txt')).toBe(true); + expect(rootFiles.some(f => f.name === 'file2.js')).toBe(true); + + // Check for directories + const directories = entries.filter(e => e.isDirectory); + expect(directories.some(d => d.name === 'subdir1')).toBe(true); + expect(directories.some(d => d.name === 'subdir2')).toBe(true); + + // Check nested structure + const subdir1 = directories.find(d => d.name === 'subdir1'); + expect(subdir1?.children).toBeDefined(); + expect(subdir1?.children?.some(c => c.name === 'file3.ts')).toBe(true); + expect( + subdir1?.children?.some(c => c.name === 'nested' && c.isDirectory), + ).toBe(true); + }); + + it('should set correct relative paths', async () => { + const entries = await fileSystemManager.listFilesRecursive(tempDir); + + const subdir1 = entries.find(e => e.name === 'subdir1' && e.isDirectory); + expect(subdir1?.relativePath).toBe('subdir1'); + + const nestedFile = subdir1?.children?.find(c => c.name === 'file3.ts'); + expect(nestedFile?.relativePath).toBe('file3.ts'); + }); + + it('should return empty array for non-existing directory', async () => { + const nonExistentDir = path.join(tempDir, 'nonexistent'); + + const entries = + await fileSystemManager.listFilesRecursive(nonExistentDir); + + expect(entries).toEqual([]); + }); + + it('should throw error when maximum depth is exceeded', async () => { + // Create a very deep directory structure (more than 15 levels) + let deepPath = tempDir; + for (let i = 0; i < 17; i++) { + deepPath = path.join(deepPath, `level${i}`); + fs.mkdirSync(deepPath, { recursive: true }); + } + + await expect( + fileSystemManager.listFilesRecursive(tempDir), + ).rejects.toMatchObject({ + type: ErrorType.FILE_SYSTEM_ERROR, + message: expect.stringContaining('Maximum directory depth exceeded'), + }); + }); + }); + + describe('copyDirectoryStructure', () => { + let sourceDir: string; + let targetDir: string; + + beforeEach(() => { + sourceDir = path.join(tempDir, 'source'); + targetDir = path.join(tempDir, 'target'); + + // Create source directory structure + const structure = { + 'file1.txt': 'content1', + 'file2.js': 'content2', + 'subdir1/file3.ts': 'content3', + 'subdir1/nested/file4.html': 'content4', + 'subdir2/file5.json': 'content5', + }; + + fs.mkdirSync(sourceDir); + for (const [filePath, content] of Object.entries(structure)) { + const fullPath = path.join(sourceDir, filePath); + fs.mkdirSync(path.dirname(fullPath), { recursive: true }); + fs.writeFileSync(fullPath, content); + } + }); + + it('should copy entire directory structure', async () => { + await fileSystemManager.copyDirectoryStructure(sourceDir, targetDir); + + // Verify target directory exists + expect(fs.existsSync(targetDir)).toBe(true); + + // Verify files are copied + expect(fs.existsSync(path.join(targetDir, 'file1.txt'))).toBe(true); + expect(fs.existsSync(path.join(targetDir, 'file2.js'))).toBe(true); + expect(fs.existsSync(path.join(targetDir, 'subdir1', 'file3.ts'))).toBe( + true, + ); + expect( + fs.existsSync(path.join(targetDir, 'subdir1', 'nested', 'file4.html')), + ).toBe(true); + expect(fs.existsSync(path.join(targetDir, 'subdir2', 'file5.json'))).toBe( + true, + ); + + // Verify content is copied correctly + expect(fs.readFileSync(path.join(targetDir, 'file1.txt'), 'utf8')).toBe( + 'content1', + ); + expect( + fs.readFileSync( + path.join(targetDir, 'subdir1', 'nested', 'file4.html'), + 'utf8', + ), + ).toBe('content4'); + }); + + it('should create target directory if it does not exist', async () => { + const deepTargetDir = path.join(tempDir, 'deep', 'nested', 'target'); + + await fileSystemManager.copyDirectoryStructure(sourceDir, deepTargetDir); + + expect(fs.existsSync(deepTargetDir)).toBe(true); + expect(fs.existsSync(path.join(deepTargetDir, 'file1.txt'))).toBe(true); + }); + + it('should throw error if source directory does not exist', async () => { + const nonExistentSource = path.join(tempDir, 'nonexistent'); + + await expect( + fileSystemManager.copyDirectoryStructure(nonExistentSource, targetDir), + ).rejects.toMatchObject({ + type: ErrorType.FILE_SYSTEM_ERROR, + message: expect.stringContaining('Source directory does not exist'), + }); + }); + }); +}); diff --git a/src/test/managers/TemplateManager.test.ts b/src/test/managers/TemplateManager.test.ts index a7c01dd..f3e0f21 100644 --- a/src/test/managers/TemplateManager.test.ts +++ b/src/test/managers/TemplateManager.test.ts @@ -1,263 +1,295 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import * as os from 'os'; -import { TemplateManager } from '../../managers/TemplateManager'; -import { FileSystemManager } from '../../managers/FileSystemManager'; -import { TemplateStructure, ProcessedTemplate, ErrorType } from '../../types'; - -describe('TemplateManager', () => { - let templateManager: TemplateManager; - let fileSystemManager: FileSystemManager; - let tempDir: string; - let templatesDir: string; - - beforeEach(() => { - fileSystemManager = new FileSystemManager(); - templateManager = new TemplateManager(fileSystemManager); - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codebot-template-test-')); - templatesDir = path.join(tempDir, 'templates'); - }); - - afterEach(() => { - // Clean up temp directory - if (fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true, force: true }); - } - }); - - describe('scanTemplateStructure', () => { - beforeEach(async () => { - // Create test template structure - const structure = { - 'component.hbs': 'export const {{name}} = () => {};', - 'component.test.hbs': 'describe("{{name}}", () => {});', - 'styles/component.css.hbs': '.{{name}} { color: red; }', - 'styles/nested/theme.css.hbs': '.{{name}}-theme { background: blue; }', - 'utils/helper.js.hbs': 'export const {{name}}Helper = () => {};' - }; - - const templatePath = path.join(templatesDir, 'react-component'); - await fileSystemManager.createFolderRecursive(templatePath); - - for (const [filePath, content] of Object.entries(structure)) { - const fullPath = path.join(templatePath, filePath); - await fileSystemManager.createFile(fullPath, content); - } - }); - - it('should scan template structure with subdirectories', async () => { - const templatePath = path.join(templatesDir, 'react-component'); - - const structure = await templateManager.scanTemplateStructure(templatePath); - - expect(structure.rootPath).toBe(templatePath); - expect(structure.files).toHaveLength(2); // component.hbs, component.test.hbs - expect(structure.directories).toHaveLength(2); // styles, utils - - // Check styles directory - const stylesDir = structure.directories.find(d => d.name === 'styles'); - expect(stylesDir).toBeDefined(); - expect(stylesDir?.files).toHaveLength(1); // component.css.hbs - expect(stylesDir?.subdirectories).toHaveLength(1); // nested - - // Check nested directory - const nestedDir = stylesDir?.subdirectories.find(d => d.name === 'nested'); - expect(nestedDir).toBeDefined(); - expect(nestedDir?.files).toHaveLength(1); // theme.css.hbs - }); - - it('should throw error for non-existent template path', async () => { - const nonExistentPath = path.join(tempDir, 'nonexistent'); - - await expect(templateManager.scanTemplateStructure(nonExistentPath)) - .rejects.toMatchObject({ - type: ErrorType.TEMPLATE_PROCESSING_ERROR - }); - }); - }); - - describe('processTemplateHierarchy', () => { - let templateStructure: TemplateStructure; - - beforeEach(async () => { - // Create test template structure - const structure = { - 'component.hbs': 'export const {{name}} = () => {};', - 'component.test.hbs': 'describe("{{name}}", () => {});', - 'styles/component.css.hbs': '.{{name}} { color: red; }', - 'utils/helper.js.hbs': 'export const {{name}}Helper = () => {};' - }; - - const templatePath = path.join(templatesDir, 'react-component'); - await fileSystemManager.createFolderRecursive(templatePath); - - for (const [filePath, content] of Object.entries(structure)) { - const fullPath = path.join(templatePath, filePath); - await fileSystemManager.createFile(fullPath, content); - } - - templateStructure = await templateManager.scanTemplateStructure(templatePath); - }); - - it('should process template hierarchy with component name', async () => { - const componentName = 'UserProfile'; - - const processed = await templateManager.processTemplateHierarchy(templateStructure, componentName); - - expect(processed.componentName).toBe(componentName); - expect(processed.files).toHaveLength(2); - expect(processed.directories).toHaveLength(2); - - // Check processed files - const componentFile = processed.files.find(f => f.targetPath.includes('component')); - expect(componentFile?.content).toContain('UserProfile'); - - // Check processed directories - const stylesDir = processed.directories.find(d => d.targetPath.includes('styles')); - expect(stylesDir).toBeDefined(); - expect(stylesDir?.files).toHaveLength(1); - - const cssFile = stylesDir?.files[0]; - expect(cssFile?.content).toContain('UserProfile'); - }); - - it('should apply naming rules to file paths', async () => { - const componentName = 'UserProfile'; - - const processed = await templateManager.processTemplateHierarchy(templateStructure, componentName); - - // Files should have .hbs extension removed - processed.files.forEach(file => { - expect(file.targetPath).not.toContain('.hbs'); - }); - - // Check if template naming is applied - const componentFile = processed.files.find(f => f.targetPath.includes('component')); - expect(componentFile?.targetPath).toContain('component'); // Should keep original name if no template placeholder - }); - }); - - describe('discoverTemplates with hierarchical support', () => { - beforeEach(async () => { - // Create multiple template types with different structures - const templates = { - 'react-component': { - 'component.hbs': 'export const {{name}} = () => {};', - 'styles/component.css.hbs': '.{{name}} { color: red; }' - }, - 'vue-component': { - 'template.vue.hbs': '', - 'assets/icon.svg.hbs': '{{name}}' - }, - 'simple-file': { - 'file.js.hbs': 'const {{name}} = {};' - } - }; - - await fileSystemManager.createFolderRecursive(templatesDir); - - for (const [templateName, files] of Object.entries(templates)) { - const templatePath = path.join(templatesDir, templateName); - await fileSystemManager.createFolderRecursive(templatePath); - - for (const [filePath, content] of Object.entries(files)) { - const fullPath = path.join(templatePath, filePath); - await fileSystemManager.createFile(fullPath, content); - } - } - }); - - it('should discover templates with subdirectory information', async () => { - // Debug: Check if templates directory exists and has content - const templatesExist = await fileSystemManager.folderExists(templatesDir); - expect(templatesExist).toBe(true); - - const templateEntries = await fileSystemManager.listFilesRecursive(templatesDir); - const templateDirs = templateEntries.filter(e => e.isDirectory); - expect(templateDirs.some(d => d.name === 'react-component')).toBe(true); - expect(templateDirs.some(d => d.name === 'vue-component')).toBe(true); - expect(templateDirs.some(d => d.name === 'simple-file')).toBe(true); - - const templates = await templateManager.discoverTemplates(tempDir); - - expect(templates).toHaveLength(3); - - // Check react-component template - const reactTemplate = templates.find(t => t.name === 'react-component'); - expect(reactTemplate?.hasSubdirectories).toBe(true); - expect(reactTemplate?.structure.directories).toHaveLength(1); - - // Check vue-component template - const vueTemplate = templates.find(t => t.name === 'vue-component'); - expect(vueTemplate?.hasSubdirectories).toBe(true); - expect(vueTemplate?.structure.directories).toHaveLength(1); - - // Check simple-file template - const simpleTemplate = templates.find(t => t.name === 'simple-file'); - expect(simpleTemplate?.hasSubdirectories).toBe(false); - expect(simpleTemplate?.structure.directories).toHaveLength(0); - }); - }); - - describe('processTemplate (updated async version)', () => { - beforeEach(async () => { - const templatePath = path.join(templatesDir, 'test-template'); - await fileSystemManager.createFolderRecursive(templatePath); - - const templateFile = path.join(templatePath, 'component.hbs'); - await fileSystemManager.createFile(templateFile, 'export const {{name}} = () => {};'); - }); - - it('should process template content with component name', async () => { - const templateFile = path.join(templatesDir, 'test-template', 'component.hbs'); - const componentName = 'UserProfile'; - - const result = await templateManager.processTemplate(templateFile, componentName); - - expect(result).toBe('export const UserProfile = () => {};'); - }); - - it('should throw error for non-existent template file', async () => { - const nonExistentFile = path.join(templatesDir, 'nonexistent.hbs'); - const componentName = 'Test'; - - await expect(templateManager.processTemplate(nonExistentFile, componentName)) - .rejects.toMatchObject({ - type: ErrorType.TEMPLATE_PROCESSING_ERROR - }); - }); - }); - - describe('cache functionality', () => { - beforeEach(async () => { - const templatePath = path.join(templatesDir, 'cached-template'); - await fileSystemManager.createFolderRecursive(templatePath); - - const templateFile = path.join(templatePath, 'component.hbs'); - await fileSystemManager.createFile(templateFile, 'export const {{name}} = () => {};'); - }); - - it('should cache template discovery results', async () => { - // First call - const templates1 = await templateManager.discoverTemplates(tempDir); - - // Second call should use cache - const templates2 = await templateManager.discoverTemplates(tempDir); - - expect(templates1).toEqual(templates2); - expect(templates1.length).toBeGreaterThanOrEqual(0); // May be 0 if no templates found - }); - - it('should invalidate cache when requested', async () => { - // Discover templates - const templates1 = await templateManager.discoverTemplates(tempDir); - - // Invalidate cache - templateManager.invalidateCache(); - - // Discover templates again - should work without errors - const templates2 = await templateManager.discoverTemplates(tempDir); - expect(templates2).toEqual(templates1); - }); - }); -}); \ No newline at end of file +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import { FileSystemManager } from '../../managers/FileSystemManager'; +import { TemplateManager } from '../../managers/TemplateManager'; +import { ErrorType, type TemplateStructure } from '../../types'; + +describe('TemplateManager', () => { + let templateManager: TemplateManager; + let fileSystemManager: FileSystemManager; + let tempDir: string; + let templatesDir: string; + + beforeEach(() => { + fileSystemManager = new FileSystemManager(); + templateManager = new TemplateManager(fileSystemManager); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codebot-template-test-')); + templatesDir = path.join(tempDir, 'templates'); + }); + + afterEach(() => { + // Clean up temp directory + if (fs.existsSync(tempDir)) { + fs.rmSync(tempDir, { recursive: true, force: true }); + } + }); + + describe('scanTemplateStructure', () => { + beforeEach(async () => { + // Create test template structure + const structure = { + 'component.hbs': 'export const {{name}} = () => {};', + 'component.test.hbs': 'describe("{{name}}", () => {});', + 'styles/component.css.hbs': '.{{name}} { color: red; }', + 'styles/nested/theme.css.hbs': '.{{name}}-theme { background: blue; }', + 'utils/helper.js.hbs': 'export const {{name}}Helper = () => {};', + }; + + const templatePath = path.join(templatesDir, 'react-component'); + await fileSystemManager.createFolderRecursive(templatePath); + + for (const [filePath, content] of Object.entries(structure)) { + const fullPath = path.join(templatePath, filePath); + await fileSystemManager.createFile(fullPath, content); + } + }); + + it('should scan template structure with subdirectories', async () => { + const templatePath = path.join(templatesDir, 'react-component'); + + const structure = + await templateManager.scanTemplateStructure(templatePath); + + expect(structure.rootPath).toBe(templatePath); + expect(structure.files).toHaveLength(2); // component.hbs, component.test.hbs + expect(structure.directories).toHaveLength(2); // styles, utils + + // Check styles directory + const stylesDir = structure.directories.find(d => d.name === 'styles'); + expect(stylesDir).toBeDefined(); + expect(stylesDir?.files).toHaveLength(1); // component.css.hbs + expect(stylesDir?.subdirectories).toHaveLength(1); // nested + + // Check nested directory + const nestedDir = stylesDir?.subdirectories.find( + d => d.name === 'nested', + ); + expect(nestedDir).toBeDefined(); + expect(nestedDir?.files).toHaveLength(1); // theme.css.hbs + }); + + it('should throw error for non-existent template path', async () => { + const nonExistentPath = path.join(tempDir, 'nonexistent'); + + await expect( + templateManager.scanTemplateStructure(nonExistentPath), + ).rejects.toMatchObject({ + type: ErrorType.TEMPLATE_PROCESSING_ERROR, + }); + }); + }); + + describe('processTemplateHierarchy', () => { + let templateStructure: TemplateStructure; + + beforeEach(async () => { + // Create test template structure + const structure = { + 'component.hbs': 'export const {{name}} = () => {};', + 'component.test.hbs': 'describe("{{name}}", () => {});', + 'styles/component.css.hbs': '.{{name}} { color: red; }', + 'utils/helper.js.hbs': 'export const {{name}}Helper = () => {};', + }; + + const templatePath = path.join(templatesDir, 'react-component'); + await fileSystemManager.createFolderRecursive(templatePath); + + for (const [filePath, content] of Object.entries(structure)) { + const fullPath = path.join(templatePath, filePath); + await fileSystemManager.createFile(fullPath, content); + } + + templateStructure = + await templateManager.scanTemplateStructure(templatePath); + }); + + it('should process template hierarchy with component name', async () => { + const componentName = 'UserProfile'; + + const processed = await templateManager.processTemplateHierarchy( + templateStructure, + componentName, + ); + + expect(processed.componentName).toBe(componentName); + expect(processed.files).toHaveLength(2); + expect(processed.directories).toHaveLength(2); + + // Check processed files + const componentFile = processed.files.find(f => + f.targetPath.includes('component'), + ); + expect(componentFile?.content).toContain('UserProfile'); + + // Check processed directories + const stylesDir = processed.directories.find(d => + d.targetPath.includes('styles'), + ); + expect(stylesDir).toBeDefined(); + expect(stylesDir?.files).toHaveLength(1); + + const cssFile = stylesDir?.files[0]; + expect(cssFile?.content).toContain('UserProfile'); + }); + + it('should apply naming rules to file paths', async () => { + const componentName = 'UserProfile'; + + const processed = await templateManager.processTemplateHierarchy( + templateStructure, + componentName, + ); + + // Files should have .hbs extension removed + for (const file of processed.files) { + expect(file.targetPath).not.toContain('.hbs'); + } + + // Check if template naming is applied + const componentFile = processed.files.find(f => + f.targetPath.includes('component'), + ); + expect(componentFile?.targetPath).toContain('component'); // Should keep original name if no template placeholder + }); + }); + + describe('discoverTemplates with hierarchical support', () => { + beforeEach(async () => { + // Create multiple template types with different structures + const templates = { + 'react-component': { + 'component.hbs': 'export const {{name}} = () => {};', + 'styles/component.css.hbs': '.{{name}} { color: red; }', + }, + 'vue-component': { + 'template.vue.hbs': '', + 'assets/icon.svg.hbs': '{{name}}', + }, + 'simple-file': { + 'file.js.hbs': 'const {{name}} = {};', + }, + }; + + await fileSystemManager.createFolderRecursive(templatesDir); + + for (const [templateName, files] of Object.entries(templates)) { + const templatePath = path.join(templatesDir, templateName); + await fileSystemManager.createFolderRecursive(templatePath); + + for (const [filePath, content] of Object.entries(files)) { + const fullPath = path.join(templatePath, filePath); + await fileSystemManager.createFile(fullPath, content); + } + } + }); + + it('should discover templates with subdirectory information', async () => { + // Debug: Check if templates directory exists and has content + const templatesExist = await fileSystemManager.folderExists(templatesDir); + expect(templatesExist).toBe(true); + + const templateEntries = + await fileSystemManager.listFilesRecursive(templatesDir); + const templateDirs = templateEntries.filter(e => e.isDirectory); + expect(templateDirs.some(d => d.name === 'react-component')).toBe(true); + expect(templateDirs.some(d => d.name === 'vue-component')).toBe(true); + expect(templateDirs.some(d => d.name === 'simple-file')).toBe(true); + + const templates = await templateManager.discoverTemplates(tempDir); + + expect(templates).toHaveLength(3); + + // Check react-component template + const reactTemplate = templates.find(t => t.name === 'react-component'); + expect(reactTemplate?.hasSubdirectories).toBe(true); + expect(reactTemplate?.structure.directories).toHaveLength(1); + + // Check vue-component template + const vueTemplate = templates.find(t => t.name === 'vue-component'); + expect(vueTemplate?.hasSubdirectories).toBe(true); + expect(vueTemplate?.structure.directories).toHaveLength(1); + + // Check simple-file template + const simpleTemplate = templates.find(t => t.name === 'simple-file'); + expect(simpleTemplate?.hasSubdirectories).toBe(false); + expect(simpleTemplate?.structure.directories).toHaveLength(0); + }); + }); + + describe('processTemplate (updated async version)', () => { + beforeEach(async () => { + const templatePath = path.join(templatesDir, 'test-template'); + await fileSystemManager.createFolderRecursive(templatePath); + + const templateFile = path.join(templatePath, 'component.hbs'); + await fileSystemManager.createFile( + templateFile, + 'export const {{name}} = () => {};', + ); + }); + + it('should process template content with component name', async () => { + const templateFile = path.join( + templatesDir, + 'test-template', + 'component.hbs', + ); + const componentName = 'UserProfile'; + + const result = await templateManager.processTemplate( + templateFile, + componentName, + ); + + expect(result).toBe('export const UserProfile = () => {};'); + }); + + it('should throw error for non-existent template file', async () => { + const nonExistentFile = path.join(templatesDir, 'nonexistent.hbs'); + const componentName = 'Test'; + + await expect( + templateManager.processTemplate(nonExistentFile, componentName), + ).rejects.toMatchObject({ + type: ErrorType.TEMPLATE_PROCESSING_ERROR, + }); + }); + }); + + describe('cache functionality', () => { + beforeEach(async () => { + const templatePath = path.join(templatesDir, 'cached-template'); + await fileSystemManager.createFolderRecursive(templatePath); + + const templateFile = path.join(templatePath, 'component.hbs'); + await fileSystemManager.createFile( + templateFile, + 'export const {{name}} = () => {};', + ); + }); + + it('should cache template discovery results', async () => { + // First call + const templates1 = await templateManager.discoverTemplates(tempDir); + + // Second call should use cache + const templates2 = await templateManager.discoverTemplates(tempDir); + + expect(templates1).toEqual(templates2); + expect(templates1.length).toBeGreaterThanOrEqual(0); // May be 0 if no templates found + }); + + it('should invalidate cache when requested', async () => { + // Discover templates + const templates1 = await templateManager.discoverTemplates(tempDir); + + // Invalidate cache + templateManager.invalidateCache(); + + // Discover templates again - should work without errors + const templates2 = await templateManager.discoverTemplates(tempDir); + expect(templates2).toEqual(templates1); + }); + }); +}); diff --git a/src/test/setup.ts b/src/test/setup.ts index c2cf087..24f89eb 100644 --- a/src/test/setup.ts +++ b/src/test/setup.ts @@ -1,12 +1,12 @@ -// Setup global para testes -import 'jest'; - -// Mock global do console para evitar logs desnecessários durante os testes -global.console = { - ...console, - log: jest.fn(), - debug: jest.fn(), - info: jest.fn(), - warn: jest.fn(), - error: jest.fn(), -}; \ No newline at end of file +// Setup global para testes +import 'jest'; + +// Mock global do console para evitar logs desnecessários durante os testes +global.console = { + ...console, + log: jest.fn(), + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), +}; diff --git a/src/test/unit/commands/updateComponent.test.ts b/src/test/unit/commands/updateComponent.test.ts index 099308b..c69c9d7 100644 --- a/src/test/unit/commands/updateComponent.test.ts +++ b/src/test/unit/commands/updateComponent.test.ts @@ -1,255 +1,297 @@ -import * as path from 'path'; -import * as fs from 'fs'; -import { updateComponent } from '../../../commands/updateComponent'; - -// Mock VS Code module -jest.mock('vscode', () => ({ - workspace: { - workspaceFolders: [ - { - uri: { - fsPath: '/test/workspace' - } - } - ] - } -})); - -// Mock helpers -jest.mock('../../../helpers', () => ({ - showMessage: jest.fn(), - showSearchDropdown: jest.fn(), - createFolder: jest.fn(), - createFile: jest.fn(), - checkExistsFile: jest.fn(), - formatTemplateName: jest.fn((templateFile, templateType, componentName) => { - return templateFile.replace('.hbs', '').replace(templateType, componentName); - }), -})); - -// Mock fs operations -jest.mock('fs', () => ({ - existsSync: jest.fn(), - readdirSync: jest.fn(), - readFileSync: jest.fn(), - promises: { - access: jest.fn(), - readdir: jest.fn(), - readFile: jest.fn(), - } -})); - -// Mock FileSystemManager -jest.mock('../../../managers/FileSystemManager', () => ({ - FileSystemManager: jest.fn().mockImplementation(() => ({ - folderExists: jest.fn().mockResolvedValue(true), - listDirectories: jest.fn().mockResolvedValue(['ComponentTemplate']), - listFiles: jest.fn().mockResolvedValue(['component.tsx.hbs', 'component.test.tsx.hbs']), - readFile: jest.fn().mockResolvedValue('Hello {{name}}!'), - listFilesRecursive: jest.fn().mockResolvedValue([ - { name: 'component.tsx.hbs', path: 'component.tsx.hbs', isDirectory: false }, - { name: 'component.test.tsx.hbs', path: 'component.test.tsx.hbs', isDirectory: false } - ]) - })) -})); - -// Mock DirectoryProcessor -jest.mock('../../../utils/DirectoryProcessor', () => ({ - DirectoryProcessor: jest.fn().mockImplementation(() => ({ - scanTemplateStructure: jest.fn().mockResolvedValue({ - rootPath: '/test/templates/ComponentTemplate', - files: [ - { name: 'component.tsx.hbs', path: 'component.tsx.hbs', relativePath: 'component.tsx.hbs' }, - { name: 'component.test.tsx.hbs', path: 'component.test.tsx.hbs', relativePath: 'component.test.tsx.hbs' } - ], - directories: [] - }), - validateDepth: jest.fn() - })) -})); - -describe('updateComponent', () => { - const mockHelpers = require('../../../helpers'); - const mockFs = fs as jest.Mocked; - - beforeEach(() => { - jest.clearAllMocks(); - - // Setup default mocks - mockFs.existsSync.mockReturnValue(true); - (mockFs.readdirSync as jest.Mock).mockReturnValue(['ComponentTemplate']); - mockFs.readFileSync.mockReturnValue('{"templateFolderPath": "templates"}'); - - // Mock successful template discovery - (mockFs.promises.access as jest.Mock).mockResolvedValue(undefined); - (mockFs.promises.readdir as jest.Mock) - .mockResolvedValue([ - { name: 'ComponentTemplate', isDirectory: () => true } - ]); - (mockFs.promises.readFile as jest.Mock).mockResolvedValue('Hello {{name}}!'); - - mockHelpers.checkExistsFile.mockReturnValue(true); - mockHelpers.showSearchDropdown.mockResolvedValue('ComponentTemplate'); - - // Reset FileSystemManager mock to default successful state - const MockedFileSystemManager = require('../../../managers/FileSystemManager').FileSystemManager; - MockedFileSystemManager.mockClear(); - MockedFileSystemManager.mockImplementation(() => ({ - folderExists: jest.fn().mockResolvedValue(true), - listDirectories: jest.fn().mockResolvedValue(['ComponentTemplate']), - listFiles: jest.fn().mockResolvedValue(['component.tsx.hbs', 'component.test.tsx.hbs']), - readFile: jest.fn().mockResolvedValue('Hello {{name}}!'), - listFilesRecursive: jest.fn().mockResolvedValue([ - { name: 'component.tsx.hbs', path: 'component.tsx.hbs', isDirectory: false }, - { name: 'component.test.tsx.hbs', path: 'component.test.tsx.hbs', isDirectory: false } - ]) - })); - - // Reset DirectoryProcessor mock to default successful state - const MockedDirectoryProcessor = require('../../../utils/DirectoryProcessor').DirectoryProcessor; - MockedDirectoryProcessor.mockClear(); - MockedDirectoryProcessor.mockImplementation(() => ({ - scanTemplateStructure: jest.fn().mockResolvedValue({ - rootPath: '/test/templates/ComponentTemplate', - files: [ - { name: 'component.tsx.hbs', path: 'component.tsx.hbs', relativePath: 'component.tsx.hbs' }, - { name: 'component.test.tsx.hbs', path: 'component.test.tsx.hbs', relativePath: 'component.test.tsx.hbs' } - ], - directories: [] - }), - validateDepth: jest.fn() - })); - }); - - it('should successfully update a component with multi-project support', async () => { - // Arrange - const args = { - fsPath: '/test/workspace/project1/components/MyComponent' - }; - - // Mock that one file exists, one doesn't - mockHelpers.checkExistsFile - .mockReturnValueOnce(true) // component folder exists - .mockReturnValueOnce(true) // first file exists (skip) - .mockReturnValueOnce(false); // second file doesn't exist (create) - - // Act - await updateComponent(args); - - // Assert - expect(mockHelpers.createFile).toHaveBeenCalledTimes(1); - expect(mockHelpers.showMessage).toHaveBeenCalledWith( - expect.stringContaining("Component 'MyComponent' updated successfully!"), - 'information' - ); - }); - - it('should handle case when no templates are found', async () => { - // Arrange - const args = { - fsPath: '/test/workspace/project1/components/MyComponent' - }; - - // Mock FileSystemManager to return no templates - const MockedFileSystemManager = require('../../../managers/FileSystemManager').FileSystemManager; - MockedFileSystemManager.mockImplementation(() => ({ - folderExists: jest.fn().mockResolvedValue(false), // No template folder exists - listDirectories: jest.fn().mockResolvedValue([]), - listFiles: jest.fn().mockResolvedValue([]), - readFile: jest.fn().mockResolvedValue('Hello {{name}}!'), - listFilesRecursive: jest.fn().mockResolvedValue([]) - })); - - // Act - await updateComponent(args); - - // Assert - expect(mockHelpers.showMessage).toHaveBeenCalledWith( - expect.stringContaining('No templates found in project'), - 'error' - ); - }); - - it('should handle invalid component name', async () => { - // Arrange - const args = { - fsPath: '/test/workspace/project1/components/' - }; - - // Mock FileSystemManager to return no templates - const MockedFileSystemManager = require('../../../managers/FileSystemManager').FileSystemManager; - MockedFileSystemManager.mockImplementation(() => ({ - folderExists: jest.fn().mockResolvedValue(false), // No template folder exists - listDirectories: jest.fn().mockResolvedValue([]), - listFiles: jest.fn().mockResolvedValue([]), - readFile: jest.fn().mockResolvedValue('Hello {{name}}!'), - listFilesRecursive: jest.fn().mockResolvedValue([]) - })); - - // Act - await updateComponent(args); - - // Assert - expect(mockHelpers.showMessage).toHaveBeenCalledWith( - expect.stringContaining('No templates found in project'), - 'error' - ); - }); - - it('should handle missing folder path', async () => { - // Arrange - const args = {}; - - // Act - await updateComponent(args); - - // Assert - expect(mockHelpers.showMessage).toHaveBeenCalledWith( - expect.stringContaining('No folder path provided'), - 'error' - ); - }); - - it('should skip existing files and only create missing ones', async () => { - // Arrange - const args = { - fsPath: '/test/workspace/project1/components/MyComponent' - }; - - // Mock file existence: file1 exists, file2 and file3 don't exist - mockHelpers.checkExistsFile - .mockReturnValueOnce(true) // component folder exists - .mockReturnValueOnce(true) // file1 exists (skip) - .mockReturnValueOnce(false); // file2 doesn't exist (create) - - // Act - await updateComponent(args); - - // Assert - expect(mockHelpers.createFile).toHaveBeenCalledTimes(1); // Only file2 - expect(mockHelpers.showMessage).toHaveBeenCalledWith( - expect.stringContaining('1 files added, 1 files already exist'), - 'information' - ); - }); - - it('should show appropriate message when all files already exist', async () => { - // Arrange - const args = { - fsPath: '/test/workspace/project1/components/MyComponent' - }; - - // Mock all files exist - mockHelpers.checkExistsFile.mockReturnValue(true); - - // Act - await updateComponent(args); - - // Assert - expect(mockHelpers.createFile).not.toHaveBeenCalled(); - expect(mockHelpers.showMessage).toHaveBeenCalledWith( - expect.stringContaining('is already up to date'), - 'information' - ); - }); -}); \ No newline at end of file +import * as fs from 'node:fs'; +import { updateComponent } from '../../../commands/updateComponent'; + +// Mock VS Code module +jest.mock('vscode', () => ({ + workspace: { + workspaceFolders: [ + { + uri: { + fsPath: '/test/workspace', + }, + }, + ], + }, +})); + +// Mock helpers +jest.mock('../../../helpers', () => ({ + showMessage: jest.fn(), + showSearchDropdown: jest.fn(), + createFolder: jest.fn(), + createFile: jest.fn(), + checkExistsFile: jest.fn(), + formatTemplateName: jest.fn((templateFile, templateType, componentName) => { + return templateFile + .replace('.hbs', '') + .replace(templateType, componentName); + }), +})); + +// Mock fs operations +jest.mock('fs', () => ({ + existsSync: jest.fn(), + readdirSync: jest.fn(), + readFileSync: jest.fn(), + promises: { + access: jest.fn(), + readdir: jest.fn(), + readFile: jest.fn(), + }, +})); + +// Mock FileSystemManager +jest.mock('../../../managers/FileSystemManager', () => ({ + FileSystemManager: jest.fn().mockImplementation(() => ({ + folderExists: jest.fn().mockResolvedValue(true), + listDirectories: jest.fn().mockResolvedValue(['ComponentTemplate']), + listFiles: jest + .fn() + .mockResolvedValue(['component.tsx.hbs', 'component.test.tsx.hbs']), + readFile: jest.fn().mockResolvedValue('Hello {{name}}!'), + listFilesRecursive: jest.fn().mockResolvedValue([ + { + name: 'component.tsx.hbs', + path: 'component.tsx.hbs', + isDirectory: false, + }, + { + name: 'component.test.tsx.hbs', + path: 'component.test.tsx.hbs', + isDirectory: false, + }, + ]), + })), +})); + +// Mock DirectoryProcessor +jest.mock('../../../utils/DirectoryProcessor', () => ({ + DirectoryProcessor: jest.fn().mockImplementation(() => ({ + scanTemplateStructure: jest.fn().mockResolvedValue({ + rootPath: '/test/templates/ComponentTemplate', + files: [ + { + name: 'component.tsx.hbs', + path: 'component.tsx.hbs', + relativePath: 'component.tsx.hbs', + }, + { + name: 'component.test.tsx.hbs', + path: 'component.test.tsx.hbs', + relativePath: 'component.test.tsx.hbs', + }, + ], + directories: [], + }), + validateDepth: jest.fn(), + })), +})); + +describe('updateComponent', () => { + const mockHelpers = require('../../../helpers'); + const mockFs = fs as jest.Mocked; + + beforeEach(() => { + jest.clearAllMocks(); + + // Setup default mocks + mockFs.existsSync.mockReturnValue(true); + (mockFs.readdirSync as jest.Mock).mockReturnValue(['ComponentTemplate']); + mockFs.readFileSync.mockReturnValue('{"templateFolderPath": "templates"}'); + + // Mock successful template discovery + (mockFs.promises.access as jest.Mock).mockResolvedValue(undefined); + (mockFs.promises.readdir as jest.Mock).mockResolvedValue([ + { name: 'ComponentTemplate', isDirectory: () => true }, + ]); + (mockFs.promises.readFile as jest.Mock).mockResolvedValue( + 'Hello {{name}}!', + ); + + mockHelpers.checkExistsFile.mockReturnValue(true); + mockHelpers.showSearchDropdown.mockResolvedValue('ComponentTemplate'); + + // Reset FileSystemManager mock to default successful state + const MockedFileSystemManager = + require('../../../managers/FileSystemManager').FileSystemManager; + MockedFileSystemManager.mockClear(); + MockedFileSystemManager.mockImplementation(() => ({ + folderExists: jest.fn().mockResolvedValue(true), + listDirectories: jest.fn().mockResolvedValue(['ComponentTemplate']), + listFiles: jest + .fn() + .mockResolvedValue(['component.tsx.hbs', 'component.test.tsx.hbs']), + readFile: jest.fn().mockResolvedValue('Hello {{name}}!'), + listFilesRecursive: jest.fn().mockResolvedValue([ + { + name: 'component.tsx.hbs', + path: 'component.tsx.hbs', + isDirectory: false, + }, + { + name: 'component.test.tsx.hbs', + path: 'component.test.tsx.hbs', + isDirectory: false, + }, + ]), + })); + + // Reset DirectoryProcessor mock to default successful state + const MockedDirectoryProcessor = + require('../../../utils/DirectoryProcessor').DirectoryProcessor; + MockedDirectoryProcessor.mockClear(); + MockedDirectoryProcessor.mockImplementation(() => ({ + scanTemplateStructure: jest.fn().mockResolvedValue({ + rootPath: '/test/templates/ComponentTemplate', + files: [ + { + name: 'component.tsx.hbs', + path: 'component.tsx.hbs', + relativePath: 'component.tsx.hbs', + }, + { + name: 'component.test.tsx.hbs', + path: 'component.test.tsx.hbs', + relativePath: 'component.test.tsx.hbs', + }, + ], + directories: [], + }), + validateDepth: jest.fn(), + })); + }); + + it('should successfully update a component with multi-project support', async () => { + // Arrange + const args = { + fsPath: '/test/workspace/project1/components/MyComponent', + }; + + // Mock that one file exists, one doesn't + mockHelpers.checkExistsFile + .mockReturnValueOnce(true) // component folder exists + .mockReturnValueOnce(true) // first file exists (skip) + .mockReturnValueOnce(false); // second file doesn't exist (create) + + // Act + await updateComponent(args); + + // Assert + expect(mockHelpers.createFile).toHaveBeenCalledTimes(1); + expect(mockHelpers.showMessage).toHaveBeenCalledWith( + expect.stringContaining("Component 'MyComponent' updated successfully!"), + 'information', + ); + }); + + it('should handle case when no templates are found', async () => { + // Arrange + const args = { + fsPath: '/test/workspace/project1/components/MyComponent', + }; + + // Mock FileSystemManager to return no templates + const MockedFileSystemManager = + require('../../../managers/FileSystemManager').FileSystemManager; + MockedFileSystemManager.mockImplementation(() => ({ + folderExists: jest.fn().mockResolvedValue(false), // No template folder exists + listDirectories: jest.fn().mockResolvedValue([]), + listFiles: jest.fn().mockResolvedValue([]), + readFile: jest.fn().mockResolvedValue('Hello {{name}}!'), + listFilesRecursive: jest.fn().mockResolvedValue([]), + })); + + // Act + await updateComponent(args); + + // Assert + expect(mockHelpers.showMessage).toHaveBeenCalledWith( + expect.stringContaining('No templates found in project'), + 'error', + ); + }); + + it('should handle invalid component name', async () => { + // Arrange + const args = { + fsPath: '/test/workspace/project1/components/', + }; + + // Mock FileSystemManager to return no templates + const MockedFileSystemManager = + require('../../../managers/FileSystemManager').FileSystemManager; + MockedFileSystemManager.mockImplementation(() => ({ + folderExists: jest.fn().mockResolvedValue(false), // No template folder exists + listDirectories: jest.fn().mockResolvedValue([]), + listFiles: jest.fn().mockResolvedValue([]), + readFile: jest.fn().mockResolvedValue('Hello {{name}}!'), + listFilesRecursive: jest.fn().mockResolvedValue([]), + })); + + // Act + await updateComponent(args); + + // Assert + expect(mockHelpers.showMessage).toHaveBeenCalledWith( + expect.stringContaining('No templates found in project'), + 'error', + ); + }); + + it('should handle missing folder path', async () => { + // Arrange + const args = {}; + + // Act + await updateComponent(args); + + // Assert + expect(mockHelpers.showMessage).toHaveBeenCalledWith( + expect.stringContaining('No folder path provided'), + 'error', + ); + }); + + it('should skip existing files and only create missing ones', async () => { + // Arrange + const args = { + fsPath: '/test/workspace/project1/components/MyComponent', + }; + + // Mock file existence: file1 exists, file2 and file3 don't exist + mockHelpers.checkExistsFile + .mockReturnValueOnce(true) // component folder exists + .mockReturnValueOnce(true) // file1 exists (skip) + .mockReturnValueOnce(false); // file2 doesn't exist (create) + + // Act + await updateComponent(args); + + // Assert + expect(mockHelpers.createFile).toHaveBeenCalledTimes(1); // Only file2 + expect(mockHelpers.showMessage).toHaveBeenCalledWith( + expect.stringContaining('1 files added, 1 files already exist'), + 'information', + ); + }); + + it('should show appropriate message when all files already exist', async () => { + // Arrange + const args = { + fsPath: '/test/workspace/project1/components/MyComponent', + }; + + // Mock all files exist + mockHelpers.checkExistsFile.mockReturnValue(true); + + // Act + await updateComponent(args); + + // Assert + expect(mockHelpers.createFile).not.toHaveBeenCalled(); + expect(mockHelpers.showMessage).toHaveBeenCalledWith( + expect.stringContaining('is already up to date'), + 'information', + ); + }); +}); diff --git a/src/test/unit/managers/ConfigurationManager.test.ts b/src/test/unit/managers/ConfigurationManager.test.ts index c1f9229..b147699 100644 --- a/src/test/unit/managers/ConfigurationManager.test.ts +++ b/src/test/unit/managers/ConfigurationManager.test.ts @@ -1,241 +1,274 @@ -import * as path from 'path'; -import { ConfigurationManager } from '../../../managers/ConfigurationManager'; -import { ConfigurationFile } from '../../../types'; -import { CodebotError } from '../../../errors'; -import { DEFAULT_CONFIGURATION } from '../../../constants'; - -// Mock fs module -jest.mock('fs', () => ({ - promises: { - readFile: jest.fn(), - writeFile: jest.fn() - }, - readFileSync: jest.fn() -})); - -// Mock vscode module -jest.mock('vscode', () => ({ - workspace: { - workspaceFolders: [ - { - uri: { - fsPath: '/mock/workspace' - } - } - ] - } -})); - -describe('ConfigurationManager', () => { - let configManager: ConfigurationManager; - const mockFs = require('fs'); - - beforeEach(() => { - jest.clearAllMocks(); - configManager = new ConfigurationManager(); - }); - - describe('getConfiguration', () => { - it('should return project-specific configuration when available', async () => { - const projectConfig: ConfigurationFile = { - templateFolderPath: 'custom-templates', - multiProject: { - enabled: false, - projectDetection: 'manual' - } - }; - - mockFs.promises.readFile.mockResolvedValueOnce(JSON.stringify(projectConfig)); - - const result = await configManager.getConfiguration('/mock/workspace/project1'); - - expect(result).toEqual(projectConfig); - expect(mockFs.promises.readFile).toHaveBeenCalledWith( - '/mock/workspace/project1/codebot.config.json', - 'utf8' - ); - }); - - it('should fallback to workspace configuration when project config not found', async () => { - const workspaceConfig: ConfigurationFile = { - templateFolderPath: 'shared-templates' - }; - - mockFs.promises.readFile - .mockRejectedValueOnce(new Error('File not found')) // Project config - .mockResolvedValueOnce(JSON.stringify(workspaceConfig)); // Workspace config - - const result = await configManager.getConfiguration('/mock/workspace/project1'); - - expect(result.templateFolderPath).toBe('shared-templates'); - }); - - it('should use default configuration when no config files found', async () => { - mockFs.promises.readFile.mockRejectedValue(new Error('File not found')); - - const result = await configManager.getConfiguration('/mock/workspace/project1'); - - expect(result).toEqual(DEFAULT_CONFIGURATION); - }); - - it('should merge configurations hierarchically', async () => { - const workspaceConfig: ConfigurationFile = { - templateFolderPath: 'workspace-templates', - multiProject: { - enabled: true, - projectDetection: 'auto' - } - }; - - const projectConfig: Partial = { - templateFolderPath: 'project-templates' - // multiProject not specified, should inherit from workspace - }; - - mockFs.promises.readFile - .mockResolvedValueOnce(JSON.stringify(projectConfig)) // Project config - .mockResolvedValueOnce(JSON.stringify(workspaceConfig)); // Workspace config - - const result = await configManager.getConfiguration('/mock/workspace/project1'); - - expect(result).toEqual({ - templateFolderPath: 'project-templates', // From project config - multiProject: { - enabled: true, - projectDetection: 'auto' - } // From workspace config - }); - }); - - it('should cache configuration results', async () => { - const config: ConfigurationFile = { templateFolderPath: 'templates' }; - mockFs.promises.readFile.mockResolvedValue(JSON.stringify(config)); - - const result1 = await configManager.getConfiguration('/mock/workspace/project1'); - const result2 = await configManager.getConfiguration('/mock/workspace/project1'); - - expect(result1).toEqual(result2); // Should have same content - expect(mockFs.promises.readFile).toHaveBeenCalledTimes(2); // Called for project and workspace - }); - - it('should return default configuration when files cannot be read', async () => { - mockFs.promises.readFile.mockRejectedValue(new Error('Permission denied')); - - const result = await configManager.getConfiguration('/mock/workspace/project1'); - - expect(result).toEqual(DEFAULT_CONFIGURATION); - }); - }); - - describe('resolveTemplatePath', () => { - it('should resolve template path using configuration', () => { - const config: ConfigurationFile = { templateFolderPath: 'custom-templates' }; - mockFs.readFileSync.mockReturnValueOnce(JSON.stringify(config)); - - const result = configManager.resolveTemplatePath('/mock/workspace/project1'); - - expect(result).toBe(path.resolve('/mock/workspace/project1', 'custom-templates')); - }); - - it('should use default template folder when config not found', () => { - mockFs.readFileSync.mockImplementation(() => { - throw new Error('File not found'); - }); - - const result = configManager.resolveTemplatePath('/mock/workspace/project1'); - - expect(result).toBe(path.resolve('/mock/workspace/project1', 'templates')); - }); - }); - - describe('validateConfiguration', () => { - it('should validate correct configuration', () => { - const validConfig: ConfigurationFile = { - templateFolderPath: 'templates', - multiProject: { - enabled: true, - projectDetection: 'auto' - } - }; - - expect(configManager.validateConfiguration(validConfig)).toBe(true); - }); - - it('should reject invalid templateFolderPath', () => { - const invalidConfig = { - templateFolderPath: 123 // Should be string - } as any; - - expect(configManager.validateConfiguration(invalidConfig)).toBe(false); - }); - - it('should reject invalid multiProject.projectDetection', () => { - const invalidConfig: ConfigurationFile = { - multiProject: { - enabled: true, - projectDetection: 'invalid' as any - } - }; - - expect(configManager.validateConfiguration(invalidConfig)).toBe(false); - }); - }); - - describe('createDefaultConfiguration', () => { - it('should create default configuration file', async () => { - await configManager.createDefaultConfiguration('/mock/workspace/project1'); - - expect(mockFs.promises.writeFile).toHaveBeenCalledWith( - '/mock/workspace/project1/codebot.config.json', - expect.stringContaining('"templateFolderPath": "templates"'), - 'utf8' - ); - }); - - it('should invalidate cache after creating configuration', async () => { - // First, cache a configuration - mockFs.promises.readFile.mockResolvedValue('{}'); - await configManager.getConfiguration('/mock/workspace/project1'); - - // Create default configuration - await configManager.createDefaultConfiguration('/mock/workspace/project1'); - - // Next call should read from file again (cache invalidated) - mockFs.promises.readFile.mockResolvedValue('{"templateFolderPath": "new-templates"}'); - await configManager.getConfiguration('/mock/workspace/project1'); - - expect(mockFs.promises.readFile).toHaveBeenCalledTimes(4); // 2 for first call + 2 for second call - }); - }); - - describe('cache management', () => { - it('should clear all cache', async () => { - mockFs.promises.readFile.mockResolvedValue('{}'); - - await configManager.getConfiguration('/mock/workspace/project1'); - await configManager.getConfiguration('/mock/workspace/project2'); - - configManager.clearCache(); - - // Next calls should read from file again - await configManager.getConfiguration('/mock/workspace/project1'); - - expect(mockFs.promises.readFile).toHaveBeenCalledTimes(6); // 2 + 2 + 2 (each call tries project and workspace) - }); - - it('should invalidate specific configuration', async () => { - mockFs.promises.readFile.mockResolvedValue('{}'); - - await configManager.getConfiguration('/mock/workspace/project1'); - await configManager.getConfiguration('/mock/workspace/project2'); - - configManager.invalidateConfiguration('/mock/workspace/project1'); - - // Only project1 should be re-read - await configManager.getConfiguration('/mock/workspace/project1'); - await configManager.getConfiguration('/mock/workspace/project2'); - - expect(mockFs.promises.readFile).toHaveBeenCalledTimes(6); // 2 + 2 + 2 (project1 re-read, project2 cached) - }); - }); -}); \ No newline at end of file +import * as path from 'node:path'; +import { DEFAULT_CONFIGURATION } from '../../../constants'; +import { ConfigurationManager } from '../../../managers/ConfigurationManager'; +import type { ConfigurationFile } from '../../../types'; + +// Mock fs module +jest.mock('fs', () => ({ + promises: { + readFile: jest.fn(), + writeFile: jest.fn(), + }, + readFileSync: jest.fn(), +})); + +// Mock vscode module +jest.mock('vscode', () => ({ + workspace: { + workspaceFolders: [ + { + uri: { + fsPath: '/mock/workspace', + }, + }, + ], + }, +})); + +describe('ConfigurationManager', () => { + let configManager: ConfigurationManager; + const mockFs = require('node:fs'); + + beforeEach(() => { + jest.clearAllMocks(); + configManager = new ConfigurationManager(); + }); + + describe('getConfiguration', () => { + it('should return project-specific configuration when available', async () => { + const projectConfig: ConfigurationFile = { + templateFolderPath: 'custom-templates', + multiProject: { + enabled: false, + projectDetection: 'manual', + }, + }; + + mockFs.promises.readFile.mockResolvedValueOnce( + JSON.stringify(projectConfig), + ); + + const result = await configManager.getConfiguration( + '/mock/workspace/project1', + ); + + expect(result).toEqual(projectConfig); + expect(mockFs.promises.readFile).toHaveBeenCalledWith( + '/mock/workspace/project1/codebot.config.json', + 'utf8', + ); + }); + + it('should fallback to workspace configuration when project config not found', async () => { + const workspaceConfig: ConfigurationFile = { + templateFolderPath: 'shared-templates', + }; + + mockFs.promises.readFile + .mockRejectedValueOnce(new Error('File not found')) // Project config + .mockResolvedValueOnce(JSON.stringify(workspaceConfig)); // Workspace config + + const result = await configManager.getConfiguration( + '/mock/workspace/project1', + ); + + expect(result.templateFolderPath).toBe('shared-templates'); + }); + + it('should use default configuration when no config files found', async () => { + mockFs.promises.readFile.mockRejectedValue(new Error('File not found')); + + const result = await configManager.getConfiguration( + '/mock/workspace/project1', + ); + + expect(result).toEqual(DEFAULT_CONFIGURATION); + }); + + it('should merge configurations hierarchically', async () => { + const workspaceConfig: ConfigurationFile = { + templateFolderPath: 'workspace-templates', + multiProject: { + enabled: true, + projectDetection: 'auto', + }, + }; + + const projectConfig: Partial = { + templateFolderPath: 'project-templates', + // multiProject not specified, should inherit from workspace + }; + + mockFs.promises.readFile + .mockResolvedValueOnce(JSON.stringify(projectConfig)) // Project config + .mockResolvedValueOnce(JSON.stringify(workspaceConfig)); // Workspace config + + const result = await configManager.getConfiguration( + '/mock/workspace/project1', + ); + + expect(result).toEqual({ + templateFolderPath: 'project-templates', // From project config + multiProject: { + enabled: true, + projectDetection: 'auto', + }, // From workspace config + }); + }); + + it('should cache configuration results', async () => { + const config: ConfigurationFile = { templateFolderPath: 'templates' }; + mockFs.promises.readFile.mockResolvedValue(JSON.stringify(config)); + + const result1 = await configManager.getConfiguration( + '/mock/workspace/project1', + ); + const result2 = await configManager.getConfiguration( + '/mock/workspace/project1', + ); + + expect(result1).toEqual(result2); // Should have same content + expect(mockFs.promises.readFile).toHaveBeenCalledTimes(2); // Called for project and workspace + }); + + it('should return default configuration when files cannot be read', async () => { + mockFs.promises.readFile.mockRejectedValue( + new Error('Permission denied'), + ); + + const result = await configManager.getConfiguration( + '/mock/workspace/project1', + ); + + expect(result).toEqual(DEFAULT_CONFIGURATION); + }); + }); + + describe('resolveTemplatePath', () => { + it('should resolve template path using configuration', () => { + const config: ConfigurationFile = { + templateFolderPath: 'custom-templates', + }; + mockFs.readFileSync.mockReturnValueOnce(JSON.stringify(config)); + + const result = configManager.resolveTemplatePath( + '/mock/workspace/project1', + ); + + expect(result).toBe( + path.resolve('/mock/workspace/project1', 'custom-templates'), + ); + }); + + it('should use default template folder when config not found', () => { + mockFs.readFileSync.mockImplementation(() => { + throw new Error('File not found'); + }); + + const result = configManager.resolveTemplatePath( + '/mock/workspace/project1', + ); + + expect(result).toBe( + path.resolve('/mock/workspace/project1', 'templates'), + ); + }); + }); + + describe('validateConfiguration', () => { + it('should validate correct configuration', () => { + const validConfig: ConfigurationFile = { + templateFolderPath: 'templates', + multiProject: { + enabled: true, + projectDetection: 'auto', + }, + }; + + expect(configManager.validateConfiguration(validConfig)).toBe(true); + }); + + it('should reject invalid templateFolderPath', () => { + const invalidConfig = { + templateFolderPath: 123, // Should be string + } as unknown as ConfigurationFile; + + expect(configManager.validateConfiguration(invalidConfig)).toBe(false); + }); + + it('should reject invalid multiProject.projectDetection', () => { + const invalidConfig: ConfigurationFile = { + multiProject: { + enabled: true, + projectDetection: 'invalid' as unknown as 'auto' | 'manual', + }, + }; + + expect(configManager.validateConfiguration(invalidConfig)).toBe(false); + }); + }); + + describe('createDefaultConfiguration', () => { + it('should create default configuration file', async () => { + await configManager.createDefaultConfiguration( + '/mock/workspace/project1', + ); + + expect(mockFs.promises.writeFile).toHaveBeenCalledWith( + '/mock/workspace/project1/codebot.config.json', + expect.stringContaining('"templateFolderPath": "templates"'), + 'utf8', + ); + }); + + it('should invalidate cache after creating configuration', async () => { + // First, cache a configuration + mockFs.promises.readFile.mockResolvedValue('{}'); + await configManager.getConfiguration('/mock/workspace/project1'); + + // Create default configuration + await configManager.createDefaultConfiguration( + '/mock/workspace/project1', + ); + + // Next call should read from file again (cache invalidated) + mockFs.promises.readFile.mockResolvedValue( + '{"templateFolderPath": "new-templates"}', + ); + await configManager.getConfiguration('/mock/workspace/project1'); + + expect(mockFs.promises.readFile).toHaveBeenCalledTimes(4); // 2 for first call + 2 for second call + }); + }); + + describe('cache management', () => { + it('should clear all cache', async () => { + mockFs.promises.readFile.mockResolvedValue('{}'); + + await configManager.getConfiguration('/mock/workspace/project1'); + await configManager.getConfiguration('/mock/workspace/project2'); + + configManager.clearCache(); + + // Next calls should read from file again + await configManager.getConfiguration('/mock/workspace/project1'); + + expect(mockFs.promises.readFile).toHaveBeenCalledTimes(6); // 2 + 2 + 2 (each call tries project and workspace) + }); + + it('should invalidate specific configuration', async () => { + mockFs.promises.readFile.mockResolvedValue('{}'); + + await configManager.getConfiguration('/mock/workspace/project1'); + await configManager.getConfiguration('/mock/workspace/project2'); + + configManager.invalidateConfiguration('/mock/workspace/project1'); + + // Only project1 should be re-read + await configManager.getConfiguration('/mock/workspace/project1'); + await configManager.getConfiguration('/mock/workspace/project2'); + + expect(mockFs.promises.readFile).toHaveBeenCalledTimes(6); // 2 + 2 + 2 (project1 re-read, project2 cached) + }); + }); +}); diff --git a/src/test/unit/managers/ProjectDetector.test.ts b/src/test/unit/managers/ProjectDetector.test.ts index 72f03c5..cb94379 100644 --- a/src/test/unit/managers/ProjectDetector.test.ts +++ b/src/test/unit/managers/ProjectDetector.test.ts @@ -1,212 +1,213 @@ -import * as path from 'path'; -import * as vscode from 'vscode'; -import { ProjectDetector } from '../../../managers/ProjectDetector'; -import { ErrorType } from '../../../types'; -import { CodebotError } from '../../../errors'; - -// Mock vscode module -jest.mock('vscode', () => ({ - workspace: { - workspaceFolders: [ - { - uri: { - fsPath: '/mock/workspace' - } - } - ] - } -})); - -// Mock fs module -jest.mock('fs', () => ({ - readdirSync: jest.fn() -})); - -describe('ProjectDetector', () => { - let projectDetector: ProjectDetector; - const mockFs = require('fs'); - - beforeEach(() => { - jest.clearAllMocks(); - - // Reset vscode mock to default state - (vscode.workspace as any).workspaceFolders = [ - { uri: { fsPath: '/mock/workspace' } } - ]; - - projectDetector = new ProjectDetector(); - }); - - describe('detectProject', () => { - it('should detect single project context', async () => { - // Mock single workspace folder - (vscode.workspace as any).workspaceFolders = [ - { uri: { fsPath: '/mock/workspace' } } - ]; - - mockFs.readdirSync.mockReturnValue([ - { name: 'src', isDirectory: () => true }, - { name: 'package.json', isDirectory: () => false } - ]); - - projectDetector = new ProjectDetector(); - const context = await projectDetector.detectProject('/mock/workspace/src'); - - expect(context).toEqual({ - workspaceRoot: '/mock/workspace', - projectRoot: '/mock/workspace', - templatePath: path.join('/mock/workspace', 'templates'), - configPath: path.join('/mock/workspace', 'codebot.config.json'), - isMultiProject: false, - projectName: undefined - }); - }); - - it('should detect multi-project context', async () => { - // Mock multiple workspace folders - (vscode.workspace as any).workspaceFolders = [ - { uri: { fsPath: '/mock/workspace/project1' } }, - { uri: { fsPath: '/mock/workspace/project2' } } - ]; - - projectDetector = new ProjectDetector(); - const context = await projectDetector.detectProject('/mock/workspace/project1/src'); - - expect(context.isMultiProject).toBe(true); - expect(context.projectName).toBeDefined(); - }); - - it('should cache project contexts', async () => { - // Mock single workspace folder - (vscode.workspace as any).workspaceFolders = [ - { uri: { fsPath: '/mock/workspace' } } - ]; - - mockFs.readdirSync.mockReturnValue([ - { name: 'src', isDirectory: () => true }, - { name: 'package.json', isDirectory: () => false } - ]); - - projectDetector = new ProjectDetector(); - const context1 = await projectDetector.detectProject('/mock/workspace/src'); - const context2 = await projectDetector.detectProject('/mock/workspace/src'); - - expect(context1).toEqual(context2); // Should have the same content - }); - - it('should handle detection errors gracefully', async () => { - // Mock workspace folders to be empty to trigger error - (vscode.workspace as any).workspaceFolders = []; - - await expect(() => new ProjectDetector()) - .toThrow(CodebotError); - }); - }); - - describe('isMultiProjectWorkspace', () => { - it('should return true for multiple workspace folders', () => { - (vscode.workspace as any).workspaceFolders = [ - { uri: { fsPath: '/mock/workspace/project1' } }, - { uri: { fsPath: '/mock/workspace/project2' } } - ]; - - projectDetector = new ProjectDetector(); - expect(projectDetector.isMultiProjectWorkspace()).toBe(true); - }); - - it('should return false for single workspace folder with no sub-projects', () => { - (vscode.workspace as any).workspaceFolders = [ - { uri: { fsPath: '/mock/workspace' } } - ]; - - mockFs.readdirSync.mockReturnValue([ - { name: 'src', isDirectory: () => true }, - { name: 'package.json', isDirectory: () => false } - ]); - - projectDetector = new ProjectDetector(); - expect(projectDetector.isMultiProjectWorkspace()).toBe(false); - }); - - it('should return true for single workspace with multiple sub-projects', () => { - (vscode.workspace as any).workspaceFolders = [ - { uri: { fsPath: '/mock/workspace' } } - ]; - - mockFs.readdirSync - .mockReturnValueOnce([ - { name: 'project1', isDirectory: () => true }, - { name: 'project2', isDirectory: () => true } - ]) - .mockReturnValue(['package.json']); // For looksLikeProject calls - - projectDetector = new ProjectDetector(); - expect(projectDetector.isMultiProjectWorkspace()).toBe(true); - }); - }); - - describe('resolveProjectRoot', () => { - it('should return workspace root for single project', () => { - mockFs.readdirSync.mockReturnValue([]); - - const projectRoot = projectDetector.resolveProjectRoot('/mock/workspace/src/components'); - expect(projectRoot).toBe('/mock/workspace'); - }); - - it('should find nearest project root in multi-project workspace', () => { - (vscode.workspace as any).workspaceFolders = [ - { uri: { fsPath: '/mock/workspace/project1' } }, - { uri: { fsPath: '/mock/workspace/project2' } } - ]; - - mockFs.readdirSync.mockReturnValue([ - { name: 'package.json', isDirectory: () => false } - ]); - - projectDetector = new ProjectDetector(); - const projectRoot = projectDetector.resolveProjectRoot('/mock/workspace/project1/src'); - - expect(projectRoot).toBe('/mock/workspace/project1'); - }); - }); - - describe('cache management', () => { - it('should clear cache', async () => { - // Mock single workspace folder - (vscode.workspace as any).workspaceFolders = [ - { uri: { fsPath: '/mock/workspace' } } - ]; - - mockFs.readdirSync.mockReturnValue([ - { name: 'src', isDirectory: () => true }, - { name: 'package.json', isDirectory: () => false } - ]); - - projectDetector = new ProjectDetector(); - await projectDetector.detectProject('/mock/workspace/src'); - expect(projectDetector.getAllProjects()).toHaveLength(1); - - projectDetector.clearCache(); - expect(projectDetector.getAllProjects()).toHaveLength(0); - }); - - it('should return all cached projects', async () => { - // Mock single workspace folder - (vscode.workspace as any).workspaceFolders = [ - { uri: { fsPath: '/mock/workspace' } } - ]; - - mockFs.readdirSync.mockReturnValue([ - { name: 'src', isDirectory: () => true }, - { name: 'package.json', isDirectory: () => false } - ]); - - projectDetector = new ProjectDetector(); - await projectDetector.detectProject('/mock/workspace/src'); - await projectDetector.detectProject('/mock/workspace/lib'); - - const projects = projectDetector.getAllProjects(); - expect(projects).toHaveLength(2); - }); - }); -}); \ No newline at end of file +import * as path from 'node:path'; +import * as vscode from 'vscode'; +import { CodebotError } from '../../../errors'; +import { ProjectDetector } from '../../../managers/ProjectDetector'; + +// Mock vscode module +jest.mock('vscode', () => ({ + workspace: { + workspaceFolders: [ + { + uri: { + fsPath: '/mock/workspace', + }, + }, + ], + }, +})); + +// Mock fs module +jest.mock('node:fs', () => ({ + readdirSync: jest.fn(), +})); + +type WorkspaceMock = { + workspaceFolders: Array<{ uri: { fsPath: string } }> | undefined; +}; + +describe('ProjectDetector', () => { + let projectDetector: ProjectDetector; + const mockFs = require('node:fs'); + const workspaceMock = vscode.workspace as unknown as WorkspaceMock; + + beforeEach(() => { + jest.clearAllMocks(); + + // Reset vscode mock to default state + workspaceMock.workspaceFolders = [{ uri: { fsPath: '/mock/workspace' } }]; + + projectDetector = new ProjectDetector(); + }); + + describe('detectProject', () => { + it('should detect single project context', async () => { + // Mock single workspace folder + workspaceMock.workspaceFolders = [{ uri: { fsPath: '/mock/workspace' } }]; + + mockFs.readdirSync.mockReturnValue([ + { name: 'src', isDirectory: () => true }, + { name: 'package.json', isDirectory: () => false }, + ]); + + projectDetector = new ProjectDetector(); + const context = await projectDetector.detectProject( + '/mock/workspace/src', + ); + + expect(context).toEqual({ + workspaceRoot: '/mock/workspace', + projectRoot: '/mock/workspace', + templatePath: path.join('/mock/workspace', 'templates'), + configPath: path.join('/mock/workspace', 'codebot.config.json'), + isMultiProject: false, + projectName: undefined, + }); + }); + + it('should detect multi-project context', async () => { + // Mock multiple workspace folders + workspaceMock.workspaceFolders = [ + { uri: { fsPath: '/mock/workspace/project1' } }, + { uri: { fsPath: '/mock/workspace/project2' } }, + ]; + + projectDetector = new ProjectDetector(); + const context = await projectDetector.detectProject( + '/mock/workspace/project1/src', + ); + + expect(context.isMultiProject).toBe(true); + expect(context.projectName).toBeDefined(); + }); + + it('should cache project contexts', async () => { + // Mock single workspace folder + workspaceMock.workspaceFolders = [{ uri: { fsPath: '/mock/workspace' } }]; + + mockFs.readdirSync.mockReturnValue([ + { name: 'src', isDirectory: () => true }, + { name: 'package.json', isDirectory: () => false }, + ]); + + projectDetector = new ProjectDetector(); + const context1 = await projectDetector.detectProject( + '/mock/workspace/src', + ); + const context2 = await projectDetector.detectProject( + '/mock/workspace/src', + ); + + expect(context1).toEqual(context2); // Should have the same content + }); + + it('should handle detection errors gracefully', async () => { + // Mock workspace folders to be empty to trigger error + workspaceMock.workspaceFolders = []; + + await expect(() => new ProjectDetector()).toThrow(CodebotError); + }); + }); + + describe('isMultiProjectWorkspace', () => { + it('should return true for multiple workspace folders', () => { + workspaceMock.workspaceFolders = [ + { uri: { fsPath: '/mock/workspace/project1' } }, + { uri: { fsPath: '/mock/workspace/project2' } }, + ]; + + projectDetector = new ProjectDetector(); + expect(projectDetector.isMultiProjectWorkspace()).toBe(true); + }); + + it('should return false for single workspace folder with no sub-projects', () => { + workspaceMock.workspaceFolders = [{ uri: { fsPath: '/mock/workspace' } }]; + + mockFs.readdirSync.mockReturnValue([ + { name: 'src', isDirectory: () => true }, + { name: 'package.json', isDirectory: () => false }, + ]); + + projectDetector = new ProjectDetector(); + expect(projectDetector.isMultiProjectWorkspace()).toBe(false); + }); + + it('should return true for single workspace with multiple sub-projects', () => { + workspaceMock.workspaceFolders = [{ uri: { fsPath: '/mock/workspace' } }]; + + mockFs.readdirSync + .mockReturnValueOnce([ + { name: 'project1', isDirectory: () => true }, + { name: 'project2', isDirectory: () => true }, + ]) + .mockReturnValue(['package.json']); // For looksLikeProject calls + + projectDetector = new ProjectDetector(); + expect(projectDetector.isMultiProjectWorkspace()).toBe(true); + }); + }); + + describe('resolveProjectRoot', () => { + it('should return workspace root for single project', () => { + mockFs.readdirSync.mockReturnValue([]); + + const projectRoot = projectDetector.resolveProjectRoot( + '/mock/workspace/src/components', + ); + expect(projectRoot).toBe('/mock/workspace'); + }); + + it('should find nearest project root in multi-project workspace', () => { + workspaceMock.workspaceFolders = [ + { uri: { fsPath: '/mock/workspace/project1' } }, + { uri: { fsPath: '/mock/workspace/project2' } }, + ]; + + mockFs.readdirSync.mockReturnValue([ + { name: 'package.json', isDirectory: () => false }, + ]); + + projectDetector = new ProjectDetector(); + const projectRoot = projectDetector.resolveProjectRoot( + '/mock/workspace/project1/src', + ); + + expect(projectRoot).toBe('/mock/workspace/project1'); + }); + }); + + describe('cache management', () => { + it('should clear cache', async () => { + // Mock single workspace folder + workspaceMock.workspaceFolders = [{ uri: { fsPath: '/mock/workspace' } }]; + + mockFs.readdirSync.mockReturnValue([ + { name: 'src', isDirectory: () => true }, + { name: 'package.json', isDirectory: () => false }, + ]); + + projectDetector = new ProjectDetector(); + await projectDetector.detectProject('/mock/workspace/src'); + expect(projectDetector.getAllProjects()).toHaveLength(1); + + projectDetector.clearCache(); + expect(projectDetector.getAllProjects()).toHaveLength(0); + }); + + it('should return all cached projects', async () => { + // Mock single workspace folder + workspaceMock.workspaceFolders = [{ uri: { fsPath: '/mock/workspace' } }]; + + mockFs.readdirSync.mockReturnValue([ + { name: 'src', isDirectory: () => true }, + { name: 'package.json', isDirectory: () => false }, + ]); + + projectDetector = new ProjectDetector(); + await projectDetector.detectProject('/mock/workspace/src'); + await projectDetector.detectProject('/mock/workspace/lib'); + + const projects = projectDetector.getAllProjects(); + expect(projects).toHaveLength(2); + }); + }); +}); diff --git a/src/test/unit/managers/TemplateManager.test.ts b/src/test/unit/managers/TemplateManager.test.ts index 87591f8..aa1566a 100644 --- a/src/test/unit/managers/TemplateManager.test.ts +++ b/src/test/unit/managers/TemplateManager.test.ts @@ -1,595 +1,796 @@ -import { TemplateManager } from '../../../managers/TemplateManager'; -import { CodebotError } from '../../../errors'; - -// Mock fs module -jest.mock('fs', () => ({ - promises: { - access: jest.fn(), - readdir: jest.fn(), - readFile: jest.fn() - }, - readFileSync: jest.fn(), - readdirSync: jest.fn() -})); - -// Mock FileSystemManager -jest.mock('../../../managers/FileSystemManager', () => ({ - FileSystemManager: jest.fn().mockImplementation(() => ({ - folderExists: jest.fn().mockResolvedValue(true), - listDirectories: jest.fn().mockResolvedValue(['ComponentSass', 'ComponentStyled']), - listFiles: jest.fn().mockResolvedValue(['Component.tsx.hbs', 'index.tsx.hbs']), - readFile: jest.fn().mockResolvedValue('Hello {{name}}!'), - listFilesRecursive: jest.fn().mockResolvedValue([ - { name: 'Component.tsx.hbs', path: 'Component.tsx.hbs', isDirectory: false }, - { name: 'index.tsx.hbs', path: 'index.tsx.hbs', isDirectory: false } - ]) - })) -})); - -// Mock DirectoryProcessor -jest.mock('../../../utils/DirectoryProcessor', () => ({ - DirectoryProcessor: jest.fn().mockImplementation(() => ({ - scanTemplateStructure: jest.fn().mockResolvedValue({ - rootPath: '/test/templates/ComponentTemplate', - files: [ - { name: 'Component.tsx.hbs', path: 'Component.tsx.hbs', relativePath: 'Component.tsx.hbs' }, - { name: 'index.tsx.hbs', path: 'index.tsx.hbs', relativePath: 'index.tsx.hbs' } - ], - directories: [] - }), - validateDepth: jest.fn() - })) -})); - -// Mock handlebars -jest.mock('handlebars', () => ({ - compile: jest.fn() -})); - -// Mock vscode module -jest.mock('vscode', () => ({ - workspace: { - workspaceFolders: [ - { - uri: { - fsPath: '/mock/workspace' - } - } - ] - } -})); - -describe('TemplateManager', () => { - let templateManager: TemplateManager; - const mockFs = require('fs'); - const mockHandlebars = require('handlebars'); - - beforeEach(() => { - jest.clearAllMocks(); - - // Reset FileSystemManager mock to default successful state - const MockedFileSystemManager = require('../../../managers/FileSystemManager').FileSystemManager; - MockedFileSystemManager.mockClear(); - MockedFileSystemManager.mockImplementation(() => ({ - folderExists: jest.fn().mockResolvedValue(true), - listDirectories: jest.fn().mockResolvedValue(['ComponentSass', 'ComponentStyled']), - listFiles: jest.fn().mockResolvedValue(['Component.tsx.hbs', 'index.tsx.hbs']), - readFile: jest.fn().mockResolvedValue('Hello {{name}}!'), - listFilesRecursive: jest.fn().mockResolvedValue([ - { name: 'Component.tsx.hbs', path: 'Component.tsx.hbs', isDirectory: false }, - { name: 'index.tsx.hbs', path: 'index.tsx.hbs', isDirectory: false } - ]) - })); - - templateManager = new TemplateManager(); - }); - - describe('discoverTemplates', () => { - it('should discover templates in project directory', async () => { - mockFs.promises.access.mockResolvedValue(undefined); - mockFs.promises.readdir - .mockResolvedValueOnce([ - { name: 'ComponentSass', isDirectory: () => true }, - { name: 'ComponentStyled', isDirectory: () => true } - ]) - .mockResolvedValueOnce(['Component.tsx.hbs', 'index.tsx.hbs']) - .mockResolvedValueOnce(['index.tsx.hbs', 'styles.ts.hbs']); - - const templates = await templateManager.discoverTemplates('/mock/workspace/project1'); - - expect(templates).toHaveLength(2); - expect(templates[0]).toEqual(expect.objectContaining({ - name: 'ComponentSass', - path: '/mock/workspace/project1/templates/ComponentSass', - files: ['Component.tsx.hbs', 'index.tsx.hbs'] - })); - expect(templates[1]).toEqual(expect.objectContaining({ - name: 'ComponentStyled', - path: '/mock/workspace/project1/templates/ComponentStyled', - files: ['Component.tsx.hbs', 'index.tsx.hbs'] - })); - }); - - it('should fallback to workspace templates when project templates not found', async () => { - // Mock FileSystemManager to return no templates for project, but templates for workspace - const MockedFileSystemManager = require('../../../managers/FileSystemManager').FileSystemManager; - MockedFileSystemManager.mockImplementation(() => ({ - folderExists: jest.fn() - .mockResolvedValueOnce(false) // Project templates don't exist - .mockResolvedValueOnce(true), // Workspace templates exist - listDirectories: jest.fn().mockResolvedValue(['SharedComponent']), - listFiles: jest.fn().mockResolvedValue(['Component.tsx.hbs']), - readFile: jest.fn().mockResolvedValue('Hello {{name}}!'), - listFilesRecursive: jest.fn().mockResolvedValue([ - { name: 'Component.tsx.hbs', path: 'Component.tsx.hbs', isDirectory: false } - ]) - })); - - const newTemplateManager = new TemplateManager(); - const templates = await newTemplateManager.discoverTemplates('/mock/workspace/project1'); - - expect(templates).toHaveLength(1); - expect(templates[0].name).toBe('SharedComponent'); - expect(templates[0].path).toBe('/mock/workspace/templates/SharedComponent'); - }); - - it('should prioritize project templates over workspace templates', async () => { - // Mock FileSystemManager to return both project and workspace templates - const MockedFileSystemManager = require('../../../managers/FileSystemManager').FileSystemManager; - MockedFileSystemManager.mockImplementation(() => ({ - folderExists: jest.fn().mockResolvedValue(true), // Both exist - listDirectories: jest.fn() - .mockResolvedValueOnce(['Component']) // Project templates - .mockResolvedValueOnce(['Component', 'OtherComponent']), // Workspace templates - listFiles: jest.fn() - .mockResolvedValueOnce(['project.tsx.hbs']) // Project Component files - .mockResolvedValueOnce(['workspace.tsx.hbs']) // Workspace Component files - .mockResolvedValueOnce(['other.tsx.hbs']), // OtherComponent files - readFile: jest.fn().mockResolvedValue('Hello {{name}}!'), - listFilesRecursive: jest.fn().mockResolvedValue([ - { name: 'project.tsx.hbs', path: 'project.tsx.hbs', isDirectory: false } - ]) - })); - - const newTemplateManager = new TemplateManager(); - const templates = await newTemplateManager.discoverTemplates('/mock/workspace/project1'); - - expect(templates).toHaveLength(2); - expect(templates[0].name).toBe('Component'); - expect(templates[0].files).toEqual(['project.tsx.hbs']); // Project version wins - expect(templates[1].name).toBe('OtherComponent'); - }); - - it('should cache template discovery results', async () => { - const templates1 = await templateManager.discoverTemplates('/mock/workspace/project1'); - const templates2 = await templateManager.discoverTemplates('/mock/workspace/project1'); - - expect(templates1).toEqual(templates2); // Should have same content - // Since we're using FileSystemManager mock, we can't easily test the fs calls - expect(templates1).toHaveLength(2); // Should return the mocked templates - }); - - it('should handle template discovery errors', async () => { - // Mock FileSystemManager to throw errors - const MockedFileSystemManager = require('../../../managers/FileSystemManager').FileSystemManager; - MockedFileSystemManager.mockImplementation(() => ({ - folderExists: jest.fn().mockRejectedValue(new Error('Permission denied')), - listDirectories: jest.fn().mockRejectedValue(new Error('Permission denied')), - listFiles: jest.fn().mockRejectedValue(new Error('Permission denied')), - readFile: jest.fn().mockRejectedValue(new Error('Permission denied')), - listFilesRecursive: jest.fn().mockRejectedValue(new Error('Permission denied')) - })); - - const newTemplateManager = new TemplateManager(); - const result = await newTemplateManager.discoverTemplates('/invalid/path'); - expect(result).toEqual([]); - }); - }); - - describe('processTemplate', () => { - it('should compile and process template with component name', async () => { - const mockTemplate = jest.fn().mockReturnValue('processed content'); - mockHandlebars.compile.mockReturnValue(mockTemplate); - - // Mock FileSystemManager readFile method - const MockedFileSystemManager = require('../../../managers/FileSystemManager').FileSystemManager; - MockedFileSystemManager.mockImplementation(() => ({ - folderExists: jest.fn().mockResolvedValue(true), - listDirectories: jest.fn().mockResolvedValue(['ComponentSass', 'ComponentStyled']), - listFiles: jest.fn().mockResolvedValue(['Component.tsx.hbs', 'index.tsx.hbs']), - readFile: jest.fn().mockResolvedValue('template content'), - listFilesRecursive: jest.fn().mockResolvedValue([ - { name: 'Component.tsx.hbs', path: 'Component.tsx.hbs', isDirectory: false }, - { name: 'index.tsx.hbs', path: 'index.tsx.hbs', isDirectory: false } - ]) - })); - - const newTemplateManager = new TemplateManager(); - const result = await newTemplateManager.processTemplate('/path/to/template.hbs', 'myComponent'); - - expect(mockHandlebars.compile).toHaveBeenCalledWith('template content'); - expect(mockTemplate).toHaveBeenCalledWith({ name: 'Mycomponent' }); - expect(result).toBe('processed content'); - }); - - it('should format component name to PascalCase', async () => { - const mockTemplate = jest.fn().mockReturnValue('processed content'); - mockHandlebars.compile.mockReturnValue(mockTemplate); - - // Mock FileSystemManager readFile method - const MockedFileSystemManager = require('../../../managers/FileSystemManager').FileSystemManager; - MockedFileSystemManager.mockImplementation(() => ({ - folderExists: jest.fn().mockResolvedValue(true), - listDirectories: jest.fn().mockResolvedValue(['ComponentSass', 'ComponentStyled']), - listFiles: jest.fn().mockResolvedValue(['Component.tsx.hbs', 'index.tsx.hbs']), - readFile: jest.fn().mockResolvedValue('template content'), - listFilesRecursive: jest.fn().mockResolvedValue([ - { name: 'Component.tsx.hbs', path: 'Component.tsx.hbs', isDirectory: false }, - { name: 'index.tsx.hbs', path: 'index.tsx.hbs', isDirectory: false } - ]) - })); - - const newTemplateManager = new TemplateManager(); - await newTemplateManager.processTemplate('/path/to/template.hbs', 'my-component-name'); - - expect(mockTemplate).toHaveBeenCalledWith({ name: 'MyComponentName' }); - }); - - it('should cache compiled templates', async () => { - const mockTemplate = jest.fn().mockReturnValue('processed content'); - mockHandlebars.compile.mockReturnValue(mockTemplate); - - // Mock FileSystemManager readFile method - const MockedFileSystemManager = require('../../../managers/FileSystemManager').FileSystemManager; - const mockReadFile = jest.fn().mockResolvedValue('template content'); - MockedFileSystemManager.mockImplementation(() => ({ - folderExists: jest.fn().mockResolvedValue(true), - listDirectories: jest.fn().mockResolvedValue(['ComponentSass', 'ComponentStyled']), - listFiles: jest.fn().mockResolvedValue(['Component.tsx.hbs', 'index.tsx.hbs']), - readFile: mockReadFile, - listFilesRecursive: jest.fn().mockResolvedValue([ - { name: 'Component.tsx.hbs', path: 'Component.tsx.hbs', isDirectory: false }, - { name: 'index.tsx.hbs', path: 'index.tsx.hbs', isDirectory: false } - ]) - })); - - const newTemplateManager = new TemplateManager(); - await newTemplateManager.processTemplate('/path/to/template.hbs', 'component1'); - await newTemplateManager.processTemplate('/path/to/template.hbs', 'component2'); - - // Template should be read and compiled only once (cached) - expect(mockReadFile).toHaveBeenCalledTimes(1); - expect(mockHandlebars.compile).toHaveBeenCalledTimes(1); // Should compile only once - expect(mockTemplate).toHaveBeenCalledTimes(2); // Should execute twice - }); - - it('should handle template processing errors', async () => { - // Mock FileSystemManager to throw errors - const MockedFileSystemManager = require('../../../managers/FileSystemManager').FileSystemManager; - MockedFileSystemManager.mockImplementation(() => ({ - folderExists: jest.fn().mockResolvedValue(true), - listDirectories: jest.fn().mockResolvedValue([]), - listFiles: jest.fn().mockResolvedValue([]), - readFile: jest.fn().mockRejectedValue(new Error('File not found')), - listFilesRecursive: jest.fn().mockResolvedValue([]) - })); - - const newTemplateManager = new TemplateManager(); - await expect(newTemplateManager.processTemplate('/invalid/template.hbs', 'component')) - .rejects - .toThrow('Error processing template'); - }); - }); - - describe('getTemplateFiles', () => { - it('should return only .hbs files', () => { - mockFs.readdirSync.mockReturnValue([ - 'Component.tsx.hbs', - 'Component.scss.hbs', - 'index.tsx.hbs', - 'README.md', - 'package.json' - ]); - - const files = templateManager.getTemplateFiles('/path/to/template'); - - expect(files).toEqual([ - 'Component.tsx.hbs', - 'Component.scss.hbs', - 'index.tsx.hbs' - ]); - }); - - it('should handle directory read errors', () => { - const originalReaddirSync = mockFs.readdirSync; - mockFs.readdirSync.mockImplementation(() => { - throw new Error('Permission denied'); - }); - - try { - expect(() => templateManager.getTemplateFiles('/invalid/path')) - .toThrow(); - } finally { - mockFs.readdirSync = originalReaddirSync; - } - }); - }); - - describe('cache management', () => { - it('should invalidate all cache', async () => { - mockFs.promises.access.mockResolvedValue(undefined); - mockFs.promises.readdir.mockResolvedValue([]); - - await templateManager.discoverTemplates('/mock/workspace/project1'); - - const statsBefore = templateManager.getCacheStats(); - expect(statsBefore.templateCacheSize).toBeGreaterThan(0); - - templateManager.invalidateCache(); - - const statsAfter = templateManager.getCacheStats(); - expect(statsAfter.templateCacheSize).toBe(0); - expect(statsAfter.compiledCacheSize).toBe(0); - }); - - it('should invalidate project-specific cache', async () => { - // Mock FileSystemManager for this test - const MockedFileSystemManager = require('../../../managers/FileSystemManager').FileSystemManager; - const mockFolderExists = jest.fn().mockResolvedValue(true); - const mockListDirectories = jest.fn().mockResolvedValue(['Component']); - - MockedFileSystemManager.mockImplementation(() => ({ - folderExists: mockFolderExists, - listDirectories: mockListDirectories, - listFiles: jest.fn().mockResolvedValue(['Component.tsx.hbs']), - readFile: jest.fn().mockResolvedValue('template content'), - listFilesRecursive: jest.fn().mockResolvedValue([ - { name: 'Component.tsx.hbs', path: 'Component.tsx.hbs', isDirectory: false } - ]) - })); - - const newTemplateManager = new TemplateManager(); - - await newTemplateManager.discoverTemplates('/mock/workspace/project1'); - await newTemplateManager.discoverTemplates('/mock/workspace/project2'); - - newTemplateManager.invalidateProjectCache('/mock/workspace/project1'); - - // Should re-read project1 but not project2 - await newTemplateManager.discoverTemplates('/mock/workspace/project1'); - await newTemplateManager.discoverTemplates('/mock/workspace/project2'); - - // Each discoverTemplates call should check folderExists for project and workspace paths - expect(mockFolderExists).toHaveBeenCalledTimes(6); // 2 + 2 + 2 (each call tries project and workspace) - }); - - it('should clean expired cache entries', async () => { - // Mock Date.now to simulate time passing - const originalNow = Date.now; - let mockTime = 1000000; - Date.now = jest.fn(() => mockTime); - - try { - mockFs.promises.access.mockResolvedValue(undefined); - mockFs.promises.readdir.mockResolvedValue([]); - - await templateManager.discoverTemplates('/mock/workspace/project1'); - - // Advance time beyond cache TTL (5 minutes) - mockTime += 6 * 60 * 1000; - - templateManager.cleanExpiredCache(); - - const stats = templateManager.getCacheStats(); - expect(stats.templateCacheSize).toBe(0); - } finally { - Date.now = originalNow; - } - }); - }); - - describe('preloadTemplates', () => { - it('should preload templates for multiple projects', async () => { - mockFs.promises.access.mockResolvedValue(undefined); - mockFs.promises.readdir.mockResolvedValue([]); - - await templateManager.preloadTemplates([ - '/mock/workspace/project1', - '/mock/workspace/project2' - ]); - - const stats = templateManager.getCacheStats(); - expect(stats.templateCacheSize).toBe(2); - }); - - it('should handle preload errors gracefully', async () => { - mockFs.promises.access.mockRejectedValue(new Error('Permission denied')); - - // Should not throw error - await expect(templateManager.preloadTemplates(['/invalid/path'])) - .resolves - .toBeUndefined(); - }); - }); - - describe('template validation', () => { - describe('validateProcessedTemplate', () => { - it('should pass validation when no duplicate target paths exist', async () => { - // Mock DirectoryProcessor to return a simple structure - const MockedDirectoryProcessor = require('../../../utils/DirectoryProcessor').DirectoryProcessor; - MockedDirectoryProcessor.mockImplementation(() => ({ - scanTemplateStructure: jest.fn().mockResolvedValue({ - rootPath: '/test/templates/ComponentTemplate', - files: [ - { name: 'Component.tsx.hbs', path: '/test/templates/ComponentTemplate/Component.tsx.hbs', relativePath: 'Component.tsx.hbs' }, - { name: 'index.tsx.hbs', path: '/test/templates/ComponentTemplate/index.tsx.hbs', relativePath: 'index.tsx.hbs' } - ], - directories: [] - }), - validateDepth: jest.fn() - })); - - const newTemplateManager = new TemplateManager(); - const templateStructure = await newTemplateManager.scanTemplateStructure('/test/templates/ComponentTemplate'); - - // This should not throw an error - await expect(newTemplateManager.processTemplateHierarchy(templateStructure, 'TestComponent')) - .resolves - .toBeDefined(); - }); - - it('should throw error when duplicate target paths are detected', async () => { - // Create a mock processed template with duplicate target paths directly - const processedTemplate = { - componentName: 'TestComponent', - files: [ - { originalPath: '/test/Component.tsx.hbs', targetPath: 'Component.tsx', content: 'content1' } - ], - directories: [ - { - originalPath: '/test/src', - targetPath: 'src', - files: [ - { originalPath: '/test/src/Component.tsx.hbs', targetPath: 'Component.tsx', content: 'content2' } - ], - subdirectories: [] - } - ] - }; - - // Test the validation method directly - expect(() => { - (templateManager as any).validateProcessedTemplate(processedTemplate); - }).toThrow('Duplicate target paths detected: Component.tsx'); - }); - - it('should handle nested directory structures correctly', async () => { - // Mock DirectoryProcessor to return a nested structure - const MockedDirectoryProcessor = require('../../../utils/DirectoryProcessor').DirectoryProcessor; - MockedDirectoryProcessor.mockImplementation(() => ({ - scanTemplateStructure: jest.fn().mockResolvedValue({ - rootPath: '/test/templates/ComponentTemplate', - files: [ - { name: 'index.tsx.hbs', path: '/test/templates/ComponentTemplate/index.tsx.hbs', relativePath: 'index.tsx.hbs' } - ], - directories: [ - { - name: 'src', - path: '/test/templates/ComponentTemplate/src', - relativePath: 'src', - files: [ - { name: 'Component.tsx.hbs', path: '/test/templates/ComponentTemplate/src/Component.tsx.hbs', relativePath: 'Component.tsx.hbs' } - ], - subdirectories: [ - { - name: 'lib', - path: '/test/templates/ComponentTemplate/src/lib', - relativePath: 'lib', - files: [ - { name: 'main.tsx.hbs', path: '/test/templates/ComponentTemplate/src/lib/main.tsx.hbs', relativePath: 'main.tsx.hbs' } - ], - subdirectories: [] - } - ] - } - ] - }), - validateDepth: jest.fn() - })); - - const newTemplateManager = new TemplateManager(); - const templateStructure = await newTemplateManager.scanTemplateStructure('/test/templates/ComponentTemplate'); - - // This should not throw an error - all paths should be unique - const result = await newTemplateManager.processTemplateHierarchy(templateStructure, 'TestComponent'); - - expect(result).toBeDefined(); - expect(result.files).toHaveLength(1); - expect(result.directories).toHaveLength(1); - expect(result.directories[0].files).toHaveLength(1); - expect(result.directories[0].subdirectories).toHaveLength(1); - expect(result.directories[0].subdirectories[0].files).toHaveLength(1); - }); - }); - - describe('collectAllTargetPaths', () => { - it('should collect paths from root files only', () => { - const processedTemplate = { - componentName: 'TestComponent', - files: [ - { originalPath: '/test/Component.tsx.hbs', targetPath: 'Component.tsx', content: 'content1' }, - { originalPath: '/test/index.tsx.hbs', targetPath: 'index.tsx', content: 'content2' } - ], - directories: [] - }; - - // Access the private method through type assertion - const paths = (templateManager as any).collectAllTargetPaths(processedTemplate); - - expect(paths).toEqual(['Component.tsx', 'index.tsx']); - }); - - it('should collect paths from nested directories', () => { - const processedTemplate = { - componentName: 'TestComponent', - files: [ - { originalPath: '/test/index.tsx.hbs', targetPath: 'index.tsx', content: 'content1' } - ], - directories: [ - { - originalPath: '/test/src', - targetPath: 'src', - files: [ - { originalPath: '/test/src/Component.tsx.hbs', targetPath: 'src/Component.tsx', content: 'content2' } - ], - subdirectories: [ - { - originalPath: '/test/src/lib', - targetPath: 'src/lib', - files: [ - { originalPath: '/test/src/lib/main.tsx.hbs', targetPath: 'src/lib/main.tsx', content: 'content3' } - ], - subdirectories: [] - } - ] - } - ] - }; - - // Access the private method through type assertion - const paths = (templateManager as any).collectAllTargetPaths(processedTemplate); - - expect(paths).toEqual(['index.tsx', 'src/Component.tsx', 'src/lib/main.tsx']); - }); - }); - - describe('findDuplicatePaths', () => { - it('should return empty array when no duplicates exist', () => { - const paths = ['Component.tsx', 'index.tsx', 'src/main.tsx']; - - // Access the private method through type assertion - const duplicates = (templateManager as any).findDuplicatePaths(paths); - - expect(duplicates).toEqual([]); - }); - - it('should find duplicate paths', () => { - const paths = ['Component.tsx', 'index.tsx', 'Component.tsx', 'src/main.tsx', 'index.tsx']; - - // Access the private method through type assertion - const duplicates = (templateManager as any).findDuplicatePaths(paths); - - expect(duplicates).toEqual(['Component.tsx', 'index.tsx']); - }); - - it('should handle empty array', () => { - const paths: string[] = []; - - // Access the private method through type assertion - const duplicates = (templateManager as any).findDuplicatePaths(paths); - - expect(duplicates).toEqual([]); - }); - }); - }); -}); \ No newline at end of file +import { TemplateManager } from '../../../managers/TemplateManager'; +import type { ProcessedTemplate } from '../../../types'; + +type TemplateManagerTestable = { + validateProcessedTemplate(processedTemplate: ProcessedTemplate): void; + collectAllTargetPaths(processedTemplate: ProcessedTemplate): string[]; + findDuplicatePaths(targetPaths: string[]): string[]; +}; + +// Mock fs module +jest.mock('fs', () => ({ + promises: { + access: jest.fn(), + readdir: jest.fn(), + readFile: jest.fn(), + }, + readFileSync: jest.fn(), + readdirSync: jest.fn(), +})); + +// Mock FileSystemManager +jest.mock('../../../managers/FileSystemManager', () => ({ + FileSystemManager: jest.fn().mockImplementation(() => ({ + folderExists: jest.fn().mockResolvedValue(true), + listDirectories: jest + .fn() + .mockResolvedValue(['ComponentSass', 'ComponentStyled']), + listFiles: jest + .fn() + .mockResolvedValue(['Component.tsx.hbs', 'index.tsx.hbs']), + readFile: jest.fn().mockResolvedValue('Hello {{name}}!'), + listFilesRecursive: jest.fn().mockResolvedValue([ + { + name: 'Component.tsx.hbs', + path: 'Component.tsx.hbs', + isDirectory: false, + }, + { name: 'index.tsx.hbs', path: 'index.tsx.hbs', isDirectory: false }, + ]), + })), +})); + +// Mock DirectoryProcessor +jest.mock('../../../utils/DirectoryProcessor', () => ({ + DirectoryProcessor: jest.fn().mockImplementation(() => ({ + scanTemplateStructure: jest.fn().mockResolvedValue({ + rootPath: '/test/templates/ComponentTemplate', + files: [ + { + name: 'Component.tsx.hbs', + path: 'Component.tsx.hbs', + relativePath: 'Component.tsx.hbs', + }, + { + name: 'index.tsx.hbs', + path: 'index.tsx.hbs', + relativePath: 'index.tsx.hbs', + }, + ], + directories: [], + }), + validateDepth: jest.fn(), + })), +})); + +// Mock handlebars +jest.mock('handlebars', () => ({ + compile: jest.fn(), +})); + +// Mock vscode module +jest.mock('vscode', () => ({ + workspace: { + workspaceFolders: [ + { + uri: { + fsPath: '/mock/workspace', + }, + }, + ], + }, +})); + +describe('TemplateManager', () => { + let templateManager: TemplateManager; + const mockFs = require('node:fs'); + const mockHandlebars = require('handlebars'); + + beforeEach(() => { + jest.clearAllMocks(); + + // Reset FileSystemManager mock to default successful state + const MockedFileSystemManager = + require('../../../managers/FileSystemManager').FileSystemManager; + MockedFileSystemManager.mockClear(); + MockedFileSystemManager.mockImplementation(() => ({ + folderExists: jest.fn().mockResolvedValue(true), + listDirectories: jest + .fn() + .mockResolvedValue(['ComponentSass', 'ComponentStyled']), + listFiles: jest + .fn() + .mockResolvedValue(['Component.tsx.hbs', 'index.tsx.hbs']), + readFile: jest.fn().mockResolvedValue('Hello {{name}}!'), + listFilesRecursive: jest.fn().mockResolvedValue([ + { + name: 'Component.tsx.hbs', + path: 'Component.tsx.hbs', + isDirectory: false, + }, + { name: 'index.tsx.hbs', path: 'index.tsx.hbs', isDirectory: false }, + ]), + })); + + templateManager = new TemplateManager(); + }); + + describe('discoverTemplates', () => { + it('should discover templates in project directory', async () => { + mockFs.promises.access.mockResolvedValue(undefined); + mockFs.promises.readdir + .mockResolvedValueOnce([ + { name: 'ComponentSass', isDirectory: () => true }, + { name: 'ComponentStyled', isDirectory: () => true }, + ]) + .mockResolvedValueOnce(['Component.tsx.hbs', 'index.tsx.hbs']) + .mockResolvedValueOnce(['index.tsx.hbs', 'styles.ts.hbs']); + + const templates = await templateManager.discoverTemplates( + '/mock/workspace/project1', + ); + + expect(templates).toHaveLength(2); + expect(templates[0]).toEqual( + expect.objectContaining({ + name: 'ComponentSass', + path: '/mock/workspace/project1/templates/ComponentSass', + files: ['Component.tsx.hbs', 'index.tsx.hbs'], + }), + ); + expect(templates[1]).toEqual( + expect.objectContaining({ + name: 'ComponentStyled', + path: '/mock/workspace/project1/templates/ComponentStyled', + files: ['Component.tsx.hbs', 'index.tsx.hbs'], + }), + ); + }); + + it('should fallback to workspace templates when project templates not found', async () => { + // Mock FileSystemManager to return no templates for project, but templates for workspace + const MockedFileSystemManager = + require('../../../managers/FileSystemManager').FileSystemManager; + MockedFileSystemManager.mockImplementation(() => ({ + folderExists: jest + .fn() + .mockResolvedValueOnce(false) // Project templates don't exist + .mockResolvedValueOnce(true), // Workspace templates exist + listDirectories: jest.fn().mockResolvedValue(['SharedComponent']), + listFiles: jest.fn().mockResolvedValue(['Component.tsx.hbs']), + readFile: jest.fn().mockResolvedValue('Hello {{name}}!'), + listFilesRecursive: jest.fn().mockResolvedValue([ + { + name: 'Component.tsx.hbs', + path: 'Component.tsx.hbs', + isDirectory: false, + }, + ]), + })); + + const newTemplateManager = new TemplateManager(); + const templates = await newTemplateManager.discoverTemplates( + '/mock/workspace/project1', + ); + + expect(templates).toHaveLength(1); + expect(templates[0].name).toBe('SharedComponent'); + expect(templates[0].path).toBe( + '/mock/workspace/templates/SharedComponent', + ); + }); + + it('should prioritize project templates over workspace templates', async () => { + // Mock FileSystemManager to return both project and workspace templates + const MockedFileSystemManager = + require('../../../managers/FileSystemManager').FileSystemManager; + MockedFileSystemManager.mockImplementation(() => ({ + folderExists: jest.fn().mockResolvedValue(true), // Both exist + listDirectories: jest + .fn() + .mockResolvedValueOnce(['Component']) // Project templates + .mockResolvedValueOnce(['Component', 'OtherComponent']), // Workspace templates + listFiles: jest + .fn() + .mockResolvedValueOnce(['project.tsx.hbs']) // Project Component files + .mockResolvedValueOnce(['workspace.tsx.hbs']) // Workspace Component files + .mockResolvedValueOnce(['other.tsx.hbs']), // OtherComponent files + readFile: jest.fn().mockResolvedValue('Hello {{name}}!'), + listFilesRecursive: jest.fn().mockResolvedValue([ + { + name: 'project.tsx.hbs', + path: 'project.tsx.hbs', + isDirectory: false, + }, + ]), + })); + + const newTemplateManager = new TemplateManager(); + const templates = await newTemplateManager.discoverTemplates( + '/mock/workspace/project1', + ); + + expect(templates).toHaveLength(2); + expect(templates[0].name).toBe('Component'); + expect(templates[0].files).toEqual(['project.tsx.hbs']); // Project version wins + expect(templates[1].name).toBe('OtherComponent'); + }); + + it('should cache template discovery results', async () => { + const templates1 = await templateManager.discoverTemplates( + '/mock/workspace/project1', + ); + const templates2 = await templateManager.discoverTemplates( + '/mock/workspace/project1', + ); + + expect(templates1).toEqual(templates2); // Should have same content + // Since we're using FileSystemManager mock, we can't easily test the fs calls + expect(templates1).toHaveLength(2); // Should return the mocked templates + }); + + it('should handle template discovery errors', async () => { + // Mock FileSystemManager to throw errors + const MockedFileSystemManager = + require('../../../managers/FileSystemManager').FileSystemManager; + MockedFileSystemManager.mockImplementation(() => ({ + folderExists: jest + .fn() + .mockRejectedValue(new Error('Permission denied')), + listDirectories: jest + .fn() + .mockRejectedValue(new Error('Permission denied')), + listFiles: jest.fn().mockRejectedValue(new Error('Permission denied')), + readFile: jest.fn().mockRejectedValue(new Error('Permission denied')), + listFilesRecursive: jest + .fn() + .mockRejectedValue(new Error('Permission denied')), + })); + + const newTemplateManager = new TemplateManager(); + const result = + await newTemplateManager.discoverTemplates('/invalid/path'); + expect(result).toEqual([]); + }); + }); + + describe('processTemplate', () => { + it('should compile and process template with component name', async () => { + const mockTemplate = jest.fn().mockReturnValue('processed content'); + mockHandlebars.compile.mockReturnValue(mockTemplate); + + // Mock FileSystemManager readFile method + const MockedFileSystemManager = + require('../../../managers/FileSystemManager').FileSystemManager; + MockedFileSystemManager.mockImplementation(() => ({ + folderExists: jest.fn().mockResolvedValue(true), + listDirectories: jest + .fn() + .mockResolvedValue(['ComponentSass', 'ComponentStyled']), + listFiles: jest + .fn() + .mockResolvedValue(['Component.tsx.hbs', 'index.tsx.hbs']), + readFile: jest.fn().mockResolvedValue('template content'), + listFilesRecursive: jest.fn().mockResolvedValue([ + { + name: 'Component.tsx.hbs', + path: 'Component.tsx.hbs', + isDirectory: false, + }, + { name: 'index.tsx.hbs', path: 'index.tsx.hbs', isDirectory: false }, + ]), + })); + + const newTemplateManager = new TemplateManager(); + const result = await newTemplateManager.processTemplate( + '/path/to/template.hbs', + 'myComponent', + ); + + expect(mockHandlebars.compile).toHaveBeenCalledWith('template content'); + expect(mockTemplate).toHaveBeenCalledWith({ name: 'Mycomponent' }); + expect(result).toBe('processed content'); + }); + + it('should format component name to PascalCase', async () => { + const mockTemplate = jest.fn().mockReturnValue('processed content'); + mockHandlebars.compile.mockReturnValue(mockTemplate); + + // Mock FileSystemManager readFile method + const MockedFileSystemManager = + require('../../../managers/FileSystemManager').FileSystemManager; + MockedFileSystemManager.mockImplementation(() => ({ + folderExists: jest.fn().mockResolvedValue(true), + listDirectories: jest + .fn() + .mockResolvedValue(['ComponentSass', 'ComponentStyled']), + listFiles: jest + .fn() + .mockResolvedValue(['Component.tsx.hbs', 'index.tsx.hbs']), + readFile: jest.fn().mockResolvedValue('template content'), + listFilesRecursive: jest.fn().mockResolvedValue([ + { + name: 'Component.tsx.hbs', + path: 'Component.tsx.hbs', + isDirectory: false, + }, + { name: 'index.tsx.hbs', path: 'index.tsx.hbs', isDirectory: false }, + ]), + })); + + const newTemplateManager = new TemplateManager(); + await newTemplateManager.processTemplate( + '/path/to/template.hbs', + 'my-component-name', + ); + + expect(mockTemplate).toHaveBeenCalledWith({ name: 'MyComponentName' }); + }); + + it('should cache compiled templates', async () => { + const mockTemplate = jest.fn().mockReturnValue('processed content'); + mockHandlebars.compile.mockReturnValue(mockTemplate); + + // Mock FileSystemManager readFile method + const MockedFileSystemManager = + require('../../../managers/FileSystemManager').FileSystemManager; + const mockReadFile = jest.fn().mockResolvedValue('template content'); + MockedFileSystemManager.mockImplementation(() => ({ + folderExists: jest.fn().mockResolvedValue(true), + listDirectories: jest + .fn() + .mockResolvedValue(['ComponentSass', 'ComponentStyled']), + listFiles: jest + .fn() + .mockResolvedValue(['Component.tsx.hbs', 'index.tsx.hbs']), + readFile: mockReadFile, + listFilesRecursive: jest.fn().mockResolvedValue([ + { + name: 'Component.tsx.hbs', + path: 'Component.tsx.hbs', + isDirectory: false, + }, + { name: 'index.tsx.hbs', path: 'index.tsx.hbs', isDirectory: false }, + ]), + })); + + const newTemplateManager = new TemplateManager(); + await newTemplateManager.processTemplate( + '/path/to/template.hbs', + 'component1', + ); + await newTemplateManager.processTemplate( + '/path/to/template.hbs', + 'component2', + ); + + // Template should be read and compiled only once (cached) + expect(mockReadFile).toHaveBeenCalledTimes(1); + expect(mockHandlebars.compile).toHaveBeenCalledTimes(1); // Should compile only once + expect(mockTemplate).toHaveBeenCalledTimes(2); // Should execute twice + }); + + it('should handle template processing errors', async () => { + // Mock FileSystemManager to throw errors + const MockedFileSystemManager = + require('../../../managers/FileSystemManager').FileSystemManager; + MockedFileSystemManager.mockImplementation(() => ({ + folderExists: jest.fn().mockResolvedValue(true), + listDirectories: jest.fn().mockResolvedValue([]), + listFiles: jest.fn().mockResolvedValue([]), + readFile: jest.fn().mockRejectedValue(new Error('File not found')), + listFilesRecursive: jest.fn().mockResolvedValue([]), + })); + + const newTemplateManager = new TemplateManager(); + await expect( + newTemplateManager.processTemplate( + '/invalid/template.hbs', + 'component', + ), + ).rejects.toThrow('Error processing template'); + }); + }); + + describe('getTemplateFiles', () => { + it('should return only .hbs files', () => { + mockFs.readdirSync.mockReturnValue([ + 'Component.tsx.hbs', + 'Component.scss.hbs', + 'index.tsx.hbs', + 'README.md', + 'package.json', + ]); + + const files = templateManager.getTemplateFiles('/path/to/template'); + + expect(files).toEqual([ + 'Component.tsx.hbs', + 'Component.scss.hbs', + 'index.tsx.hbs', + ]); + }); + + it('should handle directory read errors', () => { + const originalReaddirSync = mockFs.readdirSync; + mockFs.readdirSync.mockImplementation(() => { + throw new Error('Permission denied'); + }); + + try { + expect(() => + templateManager.getTemplateFiles('/invalid/path'), + ).toThrow(); + } finally { + mockFs.readdirSync = originalReaddirSync; + } + }); + }); + + describe('cache management', () => { + it('should invalidate all cache', async () => { + mockFs.promises.access.mockResolvedValue(undefined); + mockFs.promises.readdir.mockResolvedValue([]); + + await templateManager.discoverTemplates('/mock/workspace/project1'); + + const statsBefore = templateManager.getCacheStats(); + expect(statsBefore.templateCacheSize).toBeGreaterThan(0); + + templateManager.invalidateCache(); + + const statsAfter = templateManager.getCacheStats(); + expect(statsAfter.templateCacheSize).toBe(0); + expect(statsAfter.compiledCacheSize).toBe(0); + }); + + it('should invalidate project-specific cache', async () => { + // Mock FileSystemManager for this test + const MockedFileSystemManager = + require('../../../managers/FileSystemManager').FileSystemManager; + const mockFolderExists = jest.fn().mockResolvedValue(true); + const mockListDirectories = jest.fn().mockResolvedValue(['Component']); + + MockedFileSystemManager.mockImplementation(() => ({ + folderExists: mockFolderExists, + listDirectories: mockListDirectories, + listFiles: jest.fn().mockResolvedValue(['Component.tsx.hbs']), + readFile: jest.fn().mockResolvedValue('template content'), + listFilesRecursive: jest.fn().mockResolvedValue([ + { + name: 'Component.tsx.hbs', + path: 'Component.tsx.hbs', + isDirectory: false, + }, + ]), + })); + + const newTemplateManager = new TemplateManager(); + + await newTemplateManager.discoverTemplates('/mock/workspace/project1'); + await newTemplateManager.discoverTemplates('/mock/workspace/project2'); + + newTemplateManager.invalidateProjectCache('/mock/workspace/project1'); + + // Should re-read project1 but not project2 + await newTemplateManager.discoverTemplates('/mock/workspace/project1'); + await newTemplateManager.discoverTemplates('/mock/workspace/project2'); + + // Each discoverTemplates call should check folderExists for project and workspace paths + expect(mockFolderExists).toHaveBeenCalledTimes(6); // 2 + 2 + 2 (each call tries project and workspace) + }); + + it('should clean expired cache entries', async () => { + // Mock Date.now to simulate time passing + const originalNow = Date.now; + let mockTime = 1000000; + Date.now = jest.fn(() => mockTime); + + try { + mockFs.promises.access.mockResolvedValue(undefined); + mockFs.promises.readdir.mockResolvedValue([]); + + await templateManager.discoverTemplates('/mock/workspace/project1'); + + // Advance time beyond cache TTL (5 minutes) + mockTime += 6 * 60 * 1000; + + templateManager.cleanExpiredCache(); + + const stats = templateManager.getCacheStats(); + expect(stats.templateCacheSize).toBe(0); + } finally { + Date.now = originalNow; + } + }); + }); + + describe('preloadTemplates', () => { + it('should preload templates for multiple projects', async () => { + mockFs.promises.access.mockResolvedValue(undefined); + mockFs.promises.readdir.mockResolvedValue([]); + + await templateManager.preloadTemplates([ + '/mock/workspace/project1', + '/mock/workspace/project2', + ]); + + const stats = templateManager.getCacheStats(); + expect(stats.templateCacheSize).toBe(2); + }); + + it('should handle preload errors gracefully', async () => { + mockFs.promises.access.mockRejectedValue(new Error('Permission denied')); + + // Should not throw error + await expect( + templateManager.preloadTemplates(['/invalid/path']), + ).resolves.toBeUndefined(); + }); + }); + + describe('template validation', () => { + describe('validateProcessedTemplate', () => { + it('should pass validation when no duplicate target paths exist', async () => { + // Mock DirectoryProcessor to return a simple structure + const MockedDirectoryProcessor = + require('../../../utils/DirectoryProcessor').DirectoryProcessor; + MockedDirectoryProcessor.mockImplementation(() => ({ + scanTemplateStructure: jest.fn().mockResolvedValue({ + rootPath: '/test/templates/ComponentTemplate', + files: [ + { + name: 'Component.tsx.hbs', + path: '/test/templates/ComponentTemplate/Component.tsx.hbs', + relativePath: 'Component.tsx.hbs', + }, + { + name: 'index.tsx.hbs', + path: '/test/templates/ComponentTemplate/index.tsx.hbs', + relativePath: 'index.tsx.hbs', + }, + ], + directories: [], + }), + validateDepth: jest.fn(), + })); + + const newTemplateManager = new TemplateManager(); + const templateStructure = + await newTemplateManager.scanTemplateStructure( + '/test/templates/ComponentTemplate', + ); + + // This should not throw an error + await expect( + newTemplateManager.processTemplateHierarchy( + templateStructure, + 'TestComponent', + ), + ).resolves.toBeDefined(); + }); + + it('should throw error when duplicate target paths are detected', async () => { + // Create a mock processed template with duplicate target paths directly + const processedTemplate = { + componentName: 'TestComponent', + files: [ + { + originalPath: '/test/Component.tsx.hbs', + targetPath: 'Component.tsx', + content: 'content1', + }, + ], + directories: [ + { + originalPath: '/test/src', + targetPath: 'src', + files: [ + { + originalPath: '/test/src/Component.tsx.hbs', + targetPath: 'Component.tsx', + content: 'content2', + }, + ], + subdirectories: [], + }, + ], + }; + + // Test the validation method directly + expect(() => { + ( + templateManager as unknown as TemplateManagerTestable + ).validateProcessedTemplate(processedTemplate); + }).toThrow('Duplicate target paths detected: Component.tsx'); + }); + + it('should handle nested directory structures correctly', async () => { + // Mock DirectoryProcessor to return a nested structure + const MockedDirectoryProcessor = + require('../../../utils/DirectoryProcessor').DirectoryProcessor; + MockedDirectoryProcessor.mockImplementation(() => ({ + scanTemplateStructure: jest.fn().mockResolvedValue({ + rootPath: '/test/templates/ComponentTemplate', + files: [ + { + name: 'index.tsx.hbs', + path: '/test/templates/ComponentTemplate/index.tsx.hbs', + relativePath: 'index.tsx.hbs', + }, + ], + directories: [ + { + name: 'src', + path: '/test/templates/ComponentTemplate/src', + relativePath: 'src', + files: [ + { + name: 'Component.tsx.hbs', + path: '/test/templates/ComponentTemplate/src/Component.tsx.hbs', + relativePath: 'Component.tsx.hbs', + }, + ], + subdirectories: [ + { + name: 'lib', + path: '/test/templates/ComponentTemplate/src/lib', + relativePath: 'lib', + files: [ + { + name: 'main.tsx.hbs', + path: '/test/templates/ComponentTemplate/src/lib/main.tsx.hbs', + relativePath: 'main.tsx.hbs', + }, + ], + subdirectories: [], + }, + ], + }, + ], + }), + validateDepth: jest.fn(), + })); + + const newTemplateManager = new TemplateManager(); + const templateStructure = + await newTemplateManager.scanTemplateStructure( + '/test/templates/ComponentTemplate', + ); + + // This should not throw an error - all paths should be unique + const result = await newTemplateManager.processTemplateHierarchy( + templateStructure, + 'TestComponent', + ); + + expect(result).toBeDefined(); + expect(result.files).toHaveLength(1); + expect(result.directories).toHaveLength(1); + expect(result.directories[0].files).toHaveLength(1); + expect(result.directories[0].subdirectories).toHaveLength(1); + expect(result.directories[0].subdirectories[0].files).toHaveLength(1); + }); + }); + + describe('collectAllTargetPaths', () => { + it('should collect paths from root files only', () => { + const processedTemplate = { + componentName: 'TestComponent', + files: [ + { + originalPath: '/test/Component.tsx.hbs', + targetPath: 'Component.tsx', + content: 'content1', + }, + { + originalPath: '/test/index.tsx.hbs', + targetPath: 'index.tsx', + content: 'content2', + }, + ], + directories: [], + }; + + // Access the private method through type assertion + const paths = ( + templateManager as unknown as TemplateManagerTestable + ).collectAllTargetPaths(processedTemplate); + + expect(paths).toEqual(['Component.tsx', 'index.tsx']); + }); + + it('should collect paths from nested directories', () => { + const processedTemplate = { + componentName: 'TestComponent', + files: [ + { + originalPath: '/test/index.tsx.hbs', + targetPath: 'index.tsx', + content: 'content1', + }, + ], + directories: [ + { + originalPath: '/test/src', + targetPath: 'src', + files: [ + { + originalPath: '/test/src/Component.tsx.hbs', + targetPath: 'src/Component.tsx', + content: 'content2', + }, + ], + subdirectories: [ + { + originalPath: '/test/src/lib', + targetPath: 'src/lib', + files: [ + { + originalPath: '/test/src/lib/main.tsx.hbs', + targetPath: 'src/lib/main.tsx', + content: 'content3', + }, + ], + subdirectories: [], + }, + ], + }, + ], + }; + + // Access the private method through type assertion + const paths = ( + templateManager as unknown as TemplateManagerTestable + ).collectAllTargetPaths(processedTemplate); + + expect(paths).toEqual([ + 'index.tsx', + 'src/Component.tsx', + 'src/lib/main.tsx', + ]); + }); + }); + + describe('findDuplicatePaths', () => { + it('should return empty array when no duplicates exist', () => { + const paths = ['Component.tsx', 'index.tsx', 'src/main.tsx']; + + // Access the private method through type assertion + const duplicates = ( + templateManager as unknown as TemplateManagerTestable + ).findDuplicatePaths(paths); + + expect(duplicates).toEqual([]); + }); + + it('should find duplicate paths', () => { + const paths = [ + 'Component.tsx', + 'index.tsx', + 'Component.tsx', + 'src/main.tsx', + 'index.tsx', + ]; + + // Access the private method through type assertion + const duplicates = ( + templateManager as unknown as TemplateManagerTestable + ).findDuplicatePaths(paths); + + expect(duplicates).toEqual(['Component.tsx', 'index.tsx']); + }); + + it('should handle empty array', () => { + const paths: string[] = []; + + // Access the private method through type assertion + const duplicates = ( + templateManager as unknown as TemplateManagerTestable + ).findDuplicatePaths(paths); + + expect(duplicates).toEqual([]); + }); + }); + }); +}); diff --git a/src/test/unit/managers/TemplateManagerNaming.test.ts b/src/test/unit/managers/TemplateManagerNaming.test.ts index 8006135..4905800 100644 --- a/src/test/unit/managers/TemplateManagerNaming.test.ts +++ b/src/test/unit/managers/TemplateManagerNaming.test.ts @@ -1,168 +1,237 @@ -import { TemplateManager } from '../../../managers/TemplateManager'; - -// Mock FileSystemManager -jest.mock('../../../managers/FileSystemManager', () => ({ - FileSystemManager: jest.fn().mockImplementation(() => ({ - folderExists: jest.fn().mockResolvedValue(true), - listDirectories: jest.fn().mockResolvedValue([]), - listFiles: jest.fn().mockResolvedValue([]), - readFile: jest.fn().mockResolvedValue('Hello {{name}}!'), - listFilesRecursive: jest.fn().mockResolvedValue([]) - })) -})); - -// Mock DirectoryProcessor -jest.mock('../../../utils/DirectoryProcessor', () => ({ - DirectoryProcessor: jest.fn().mockImplementation(() => ({ - scanTemplateStructure: jest.fn().mockResolvedValue({ - rootPath: '/test/templates/ComponentSass', - files: [], - directories: [] - }), - validateDepth: jest.fn() - })) -})); - -describe('TemplateManager - New Naming Rules', () => { - let templateManager: TemplateManager; - - beforeEach(() => { - jest.clearAllMocks(); - templateManager = new TemplateManager(); - }); - - describe('applyNamingRules with template folder name', () => { - it('should replace file name starting with template folder name', () => { - // Access the private method for testing - const applyNamingRules = (templateManager as any).applyNamingRules.bind(templateManager); - - const result = applyNamingRules('ComponentSass.stories.tsx', 'Page', 'ComponentSass'); - expect(result).toBe('Page.stories.tsx'); - }); - - it('should replace file name with different component name', () => { - const applyNamingRules = (templateManager as any).applyNamingRules.bind(templateManager); - - const result = applyNamingRules('ComponentSass.module.scss', 'UserProfile', 'ComponentSass'); - expect(result).toBe('UserProfile.module.scss'); - }); - - it('should handle case insensitive matching', () => { - const applyNamingRules = (templateManager as any).applyNamingRules.bind(templateManager); - - const result = applyNamingRules('componentsass.test.tsx', 'Page', 'ComponentSass'); - expect(result).toBe('Page.test.tsx'); - }); - - it('should not change files that do not start with template folder name', () => { - const applyNamingRules = (templateManager as any).applyNamingRules.bind(templateManager); - - const result = applyNamingRules('index.tsx', 'Page', 'ComponentSass'); - expect(result).toBe('index.tsx'); - }); - - it('should preserve existing template/Template replacement functionality', () => { - const applyNamingRules = (templateManager as any).applyNamingRules.bind(templateManager); - - const result = applyNamingRules('template.config.js', 'Page', 'ComponentSass'); - expect(result).toBe('page.config.js'); - }); - - it('should handle both template folder name and template keyword', () => { - const applyNamingRules = (templateManager as any).applyNamingRules.bind(templateManager); - - // File starts with template folder name AND contains template keyword - const result = applyNamingRules('ComponentSass.template.tsx', 'Page', 'ComponentSass'); - expect(result).toBe('Page.page.tsx'); - }); - - it('should work without template folder name (fallback to existing behavior)', () => { - const applyNamingRules = (templateManager as any).applyNamingRules.bind(templateManager); - - const result = applyNamingRules('ComponentSass.stories.tsx', 'Page'); - expect(result).toBe('ComponentSass.stories.tsx'); - }); - - it('should handle empty or undefined template folder name', () => { - const applyNamingRules = (templateManager as any).applyNamingRules.bind(templateManager); - - const result1 = applyNamingRules('ComponentSass.stories.tsx', 'Page', ''); - expect(result1).toBe('ComponentSass.stories.tsx'); - - const result2 = applyNamingRules('ComponentSass.stories.tsx', 'Page', undefined); - expect(result2).toBe('ComponentSass.stories.tsx'); - }); - }); - - describe('extractTemplateFolderName', () => { - it('should extract folder name from Unix path', () => { - const extractTemplateFolderName = (templateManager as any).extractTemplateFolderName.bind(templateManager); - - const result = extractTemplateFolderName('/path/to/templates/ComponentSass'); - expect(result).toBe('ComponentSass'); - }); - - it('should extract folder name from Windows path', () => { - const extractTemplateFolderName = (templateManager as any).extractTemplateFolderName.bind(templateManager); - - const result = extractTemplateFolderName('C:\\templates\\ComponentStyled'); - expect(result).toBe('ComponentStyled'); - }); - - it('should extract folder name from relative path', () => { - const extractTemplateFolderName = (templateManager as any).extractTemplateFolderName.bind(templateManager); - - const result = extractTemplateFolderName('ComponentSass'); - expect(result).toBe('ComponentSass'); - }); - - it('should return undefined for invalid paths', () => { - const extractTemplateFolderName = (templateManager as any).extractTemplateFolderName.bind(templateManager); - - const result1 = extractTemplateFolderName(''); - expect(result1).toBeUndefined(); - - const result2 = extractTemplateFolderName('.'); - expect(result2).toBeUndefined(); - - const result3 = extractTemplateFolderName('..'); - expect(result3).toBeUndefined(); - }); - - it('should handle errors gracefully', () => { - const extractTemplateFolderName = (templateManager as any).extractTemplateFolderName.bind(templateManager); - - // This should not throw an error - const result = extractTemplateFolderName(null); - expect(result).toBeUndefined(); - }); - }); - - describe('integration with processTemplateHierarchy', () => { - it('should extract template folder name and pass it through the processing chain', async () => { - // Mock DirectoryProcessor to return a structure with files - const MockedDirectoryProcessor = require('../../../utils/DirectoryProcessor').DirectoryProcessor; - MockedDirectoryProcessor.mockImplementation(() => ({ - scanTemplateStructure: jest.fn().mockResolvedValue({ - rootPath: '/test/templates/ComponentSass', - files: [ - { - name: 'ComponentSass.stories.tsx.hbs', - path: '/test/templates/ComponentSass/ComponentSass.stories.tsx.hbs', - relativePath: 'ComponentSass.stories.tsx.hbs' - } - ], - directories: [] - }), - validateDepth: jest.fn() - })); - - const newTemplateManager = new TemplateManager(); - const templateStructure = await newTemplateManager.scanTemplateStructure('/test/templates/ComponentSass'); - const result = await newTemplateManager.processTemplateHierarchy(templateStructure, 'Page'); - - expect(result.files).toHaveLength(1); - expect(result.files[0].targetPath).toBe('Page.stories.tsx'); - }); - }); -}); \ No newline at end of file +import { TemplateManager } from '../../../managers/TemplateManager'; + +type TemplateManagerTestable = { + applyNamingRules( + filePath: string, + componentName: string, + templateFolderName?: string, + ): string; + extractTemplateFolderName(templateRootPath: string): string | undefined; +}; + +// Mock FileSystemManager +jest.mock('../../../managers/FileSystemManager', () => ({ + FileSystemManager: jest.fn().mockImplementation(() => ({ + folderExists: jest.fn().mockResolvedValue(true), + listDirectories: jest.fn().mockResolvedValue([]), + listFiles: jest.fn().mockResolvedValue([]), + readFile: jest.fn().mockResolvedValue('Hello {{name}}!'), + listFilesRecursive: jest.fn().mockResolvedValue([]), + })), +})); + +// Mock DirectoryProcessor +jest.mock('../../../utils/DirectoryProcessor', () => ({ + DirectoryProcessor: jest.fn().mockImplementation(() => ({ + scanTemplateStructure: jest.fn().mockResolvedValue({ + rootPath: '/test/templates/ComponentSass', + files: [], + directories: [], + }), + validateDepth: jest.fn(), + })), +})); + +describe('TemplateManager - New Naming Rules', () => { + let templateManager: TemplateManager; + + beforeEach(() => { + jest.clearAllMocks(); + templateManager = new TemplateManager(); + }); + + describe('applyNamingRules with template folder name', () => { + it('should replace file name starting with template folder name', () => { + // Access the private method for testing + const applyNamingRules = ( + templateManager as unknown as TemplateManagerTestable + ).applyNamingRules.bind(templateManager); + + const result = applyNamingRules( + 'ComponentSass.stories.tsx', + 'Page', + 'ComponentSass', + ); + expect(result).toBe('Page.stories.tsx'); + }); + + it('should replace file name with different component name', () => { + const applyNamingRules = ( + templateManager as unknown as TemplateManagerTestable + ).applyNamingRules.bind(templateManager); + + const result = applyNamingRules( + 'ComponentSass.module.scss', + 'UserProfile', + 'ComponentSass', + ); + expect(result).toBe('UserProfile.module.scss'); + }); + + it('should handle case insensitive matching', () => { + const applyNamingRules = ( + templateManager as unknown as TemplateManagerTestable + ).applyNamingRules.bind(templateManager); + + const result = applyNamingRules( + 'componentsass.test.tsx', + 'Page', + 'ComponentSass', + ); + expect(result).toBe('Page.test.tsx'); + }); + + it('should not change files that do not start with template folder name', () => { + const applyNamingRules = ( + templateManager as unknown as TemplateManagerTestable + ).applyNamingRules.bind(templateManager); + + const result = applyNamingRules('index.tsx', 'Page', 'ComponentSass'); + expect(result).toBe('index.tsx'); + }); + + it('should preserve existing template/Template replacement functionality', () => { + const applyNamingRules = ( + templateManager as unknown as TemplateManagerTestable + ).applyNamingRules.bind(templateManager); + + const result = applyNamingRules( + 'template.config.js', + 'Page', + 'ComponentSass', + ); + expect(result).toBe('page.config.js'); + }); + + it('should handle both template folder name and template keyword', () => { + const applyNamingRules = ( + templateManager as unknown as TemplateManagerTestable + ).applyNamingRules.bind(templateManager); + + // File starts with template folder name AND contains template keyword + const result = applyNamingRules( + 'ComponentSass.template.tsx', + 'Page', + 'ComponentSass', + ); + expect(result).toBe('Page.page.tsx'); + }); + + it('should work without template folder name (fallback to existing behavior)', () => { + const applyNamingRules = ( + templateManager as unknown as TemplateManagerTestable + ).applyNamingRules.bind(templateManager); + + const result = applyNamingRules('ComponentSass.stories.tsx', 'Page'); + expect(result).toBe('ComponentSass.stories.tsx'); + }); + + it('should handle empty or undefined template folder name', () => { + const applyNamingRules = ( + templateManager as unknown as TemplateManagerTestable + ).applyNamingRules.bind(templateManager); + + const result1 = applyNamingRules('ComponentSass.stories.tsx', 'Page', ''); + expect(result1).toBe('ComponentSass.stories.tsx'); + + const result2 = applyNamingRules( + 'ComponentSass.stories.tsx', + 'Page', + undefined, + ); + expect(result2).toBe('ComponentSass.stories.tsx'); + }); + }); + + describe('extractTemplateFolderName', () => { + it('should extract folder name from Unix path', () => { + const extractTemplateFolderName = ( + templateManager as unknown as TemplateManagerTestable + ).extractTemplateFolderName.bind(templateManager); + + const result = extractTemplateFolderName( + '/path/to/templates/ComponentSass', + ); + expect(result).toBe('ComponentSass'); + }); + + it('should extract folder name from Windows path', () => { + const extractTemplateFolderName = ( + templateManager as unknown as TemplateManagerTestable + ).extractTemplateFolderName.bind(templateManager); + + const result = extractTemplateFolderName( + 'C:\\templates\\ComponentStyled', + ); + expect(result).toBe('ComponentStyled'); + }); + + it('should extract folder name from relative path', () => { + const extractTemplateFolderName = ( + templateManager as unknown as TemplateManagerTestable + ).extractTemplateFolderName.bind(templateManager); + + const result = extractTemplateFolderName('ComponentSass'); + expect(result).toBe('ComponentSass'); + }); + + it('should return undefined for invalid paths', () => { + const extractTemplateFolderName = ( + templateManager as unknown as TemplateManagerTestable + ).extractTemplateFolderName.bind(templateManager); + + const result1 = extractTemplateFolderName(''); + expect(result1).toBeUndefined(); + + const result2 = extractTemplateFolderName('.'); + expect(result2).toBeUndefined(); + + const result3 = extractTemplateFolderName('..'); + expect(result3).toBeUndefined(); + }); + + it('should handle errors gracefully', () => { + const extractTemplateFolderName = ( + templateManager as unknown as TemplateManagerTestable + ).extractTemplateFolderName.bind(templateManager); + + // This should not throw an error + const result = extractTemplateFolderName(null as unknown as string); + expect(result).toBeUndefined(); + }); + }); + + describe('integration with processTemplateHierarchy', () => { + it('should extract template folder name and pass it through the processing chain', async () => { + // Mock DirectoryProcessor to return a structure with files + const MockedDirectoryProcessor = + require('../../../utils/DirectoryProcessor').DirectoryProcessor; + MockedDirectoryProcessor.mockImplementation(() => ({ + scanTemplateStructure: jest.fn().mockResolvedValue({ + rootPath: '/test/templates/ComponentSass', + files: [ + { + name: 'ComponentSass.stories.tsx.hbs', + path: '/test/templates/ComponentSass/ComponentSass.stories.tsx.hbs', + relativePath: 'ComponentSass.stories.tsx.hbs', + }, + ], + directories: [], + }), + validateDepth: jest.fn(), + })); + + const newTemplateManager = new TemplateManager(); + const templateStructure = await newTemplateManager.scanTemplateStructure( + '/test/templates/ComponentSass', + ); + const result = await newTemplateManager.processTemplateHierarchy( + templateStructure, + 'Page', + ); + + expect(result.files).toHaveLength(1); + expect(result.files[0].targetPath).toBe('Page.stories.tsx'); + }); + }); +}); diff --git a/src/test/unit/utils/PathResolver.integration.test.ts b/src/test/unit/utils/PathResolver.integration.test.ts index 15fbba9..314f917 100644 --- a/src/test/unit/utils/PathResolver.integration.test.ts +++ b/src/test/unit/utils/PathResolver.integration.test.ts @@ -1,235 +1,298 @@ -import { PathResolver } from '../../../utils/PathResolver'; -import { ProjectContext } from '../../../types'; - -describe('PathResolver Integration Tests', () => { - let pathResolver: PathResolver; - - beforeEach(() => { - pathResolver = new PathResolver(); - }); - - describe('Multi-project workspace scenarios', () => { - it('should resolve paths correctly for nested project structure', () => { - const nestedProjectContext: ProjectContext = { - workspaceRoot: '/workspace', - projectRoot: '/workspace/apps/frontend', - templatePath: '/workspace/apps/frontend/templates', - configPath: '/workspace/apps/frontend/codebot.config.json', - isMultiProject: true, - projectName: 'frontend' - }; - - const templatePath = pathResolver.resolveTemplatePath(nestedProjectContext, 'ReactComponent'); - expect(templatePath).toBe('/workspace/apps/frontend/templates/ReactComponent'); - - const targetPath = pathResolver.resolveTargetPath(nestedProjectContext, 'MyComponent'); - expect(targetPath).toBe('/workspace/apps/frontend/MyComponent'); - - const configPath = pathResolver.resolveConfigPath(nestedProjectContext); - expect(configPath).toBe('/workspace/apps/frontend/codebot.config.json'); - }); - - it('should resolve paths correctly for monorepo with packages', () => { - const packageProjectContext: ProjectContext = { - workspaceRoot: '/monorepo', - projectRoot: '/monorepo/packages/ui-components', - templatePath: '/monorepo/packages/ui-components/templates', - configPath: '/monorepo/packages/ui-components/codebot.config.json', - isMultiProject: true, - projectName: 'ui-components' - }; - - const templatePath = pathResolver.resolveTemplatePath(packageProjectContext, 'Component'); - expect(templatePath).toBe('/monorepo/packages/ui-components/templates/Component'); - - const relativePath = pathResolver.resolveRelativePath(packageProjectContext, 'src/components'); - expect(relativePath).toBe('/monorepo/packages/ui-components/src/components'); - }); - - it('should handle deeply nested project structures', () => { - const deepProjectContext: ProjectContext = { - workspaceRoot: '/company', - projectRoot: '/company/projects/web/apps/admin/modules/user-management', - templatePath: '/company/projects/web/apps/admin/modules/user-management/templates', - configPath: '/company/projects/web/apps/admin/modules/user-management/codebot.config.json', - isMultiProject: true, - projectName: 'user-management' - }; - - const targetPath = pathResolver.resolveTargetPath(deepProjectContext, 'UserProfile'); - expect(targetPath).toBe('/company/projects/web/apps/admin/modules/user-management/UserProfile'); - - // Ensure boundary validation works with deep paths - expect(() => { - pathResolver.resolveRelativePath(deepProjectContext, '../../../../../malicious'); - }).toThrow(); - }); - }); - - describe('Single project workspace scenarios', () => { - it('should resolve paths correctly for simple single project', () => { - const singleProjectContext: ProjectContext = { - workspaceRoot: '/simple-app', - projectRoot: '/simple-app', - templatePath: '/simple-app/templates', - configPath: '/simple-app/codebot.config.json', - isMultiProject: false - }; - - const templatePath = pathResolver.resolveTemplatePath(singleProjectContext, 'Component'); - expect(templatePath).toBe('/simple-app/templates/Component'); - - const targetPath = pathResolver.resolveTargetPath(singleProjectContext, 'MyComponent'); - expect(targetPath).toBe('/simple-app/MyComponent'); - }); - - it('should handle single project with complex folder structure', () => { - const complexSingleProject: ProjectContext = { - workspaceRoot: '/complex-app', - projectRoot: '/complex-app', - templatePath: '/complex-app/templates', - configPath: '/complex-app/codebot.config.json', - isMultiProject: false - }; - - const relativePath = pathResolver.resolveRelativePath(complexSingleProject, 'src/features/auth/components'); - expect(relativePath).toBe('/complex-app/src/features/auth/components'); - }); - }); - - describe('Edge cases and security', () => { - it('should prevent path traversal across project boundaries', () => { - const restrictedContext: ProjectContext = { - workspaceRoot: '/workspace', - projectRoot: '/workspace/project-a', - templatePath: '/workspace/project-a/templates', - configPath: '/workspace/project-a/codebot.config.json', - isMultiProject: true, - projectName: 'project-a' - }; - - // These should all throw errors due to path traversal attempts - expect(() => { - pathResolver.resolveRelativePath(restrictedContext, '../project-b/secrets'); - }).toThrow(); - - expect(() => { - pathResolver.resolveRelativePath(restrictedContext, '../../etc/passwd'); - }).toThrow(); - - expect(() => { - pathResolver.resolveTargetPath(restrictedContext, '../../../malicious'); - }).toThrow(); - }); - - it('should handle Windows-style paths in multi-project setup', () => { - const windowsContext: ProjectContext = { - workspaceRoot: 'C:\\workspace', - projectRoot: 'C:\\workspace\\project1', - templatePath: 'C:\\workspace\\project1\\templates', - configPath: 'C:\\workspace\\project1\\codebot.config.json', - isMultiProject: true, - projectName: 'project1' - }; - - const targetPath = pathResolver.resolveTargetPath(windowsContext, 'Component'); - expect(targetPath).toContain('Component'); - expect(targetPath).toContain('project1'); - }); - - it('should sanitize component names while preserving project structure', () => { - const projectContext: ProjectContext = { - workspaceRoot: '/workspace', - projectRoot: '/workspace/frontend', - templatePath: '/workspace/frontend/templates', - configPath: '/workspace/frontend/codebot.config.json', - isMultiProject: true, - projectName: 'frontend' - }; - - const targetPath = pathResolver.resolveTargetPath(projectContext, 'MyName'); - expect(targetPath).toBe('/workspace/frontend/My_Component_Name'); - - const templatePath = pathResolver.resolveTemplatePath(projectContext, 'React|Component'); - expect(templatePath).toBe('/workspace/frontend/templates/React_Component'); - }); - - it('should handle Unicode characters in project paths', () => { - const unicodeContext: ProjectContext = { - workspaceRoot: '/workspace', - projectRoot: '/workspace/项目-α', - templatePath: '/workspace/项目-α/templates', - configPath: '/workspace/项目-α/codebot.config.json', - isMultiProject: true, - projectName: '项目-α' - }; - - const targetPath = pathResolver.resolveTargetPath(unicodeContext, 'Component测试'); - expect(targetPath).toBe('/workspace/项目-α/Component测试'); - }); - }); - - describe('Performance and limits', () => { - it('should handle reasonable number of nested folders', () => { - const deepContext: ProjectContext = { - workspaceRoot: '/workspace', - projectRoot: '/workspace/project', - templatePath: '/workspace/project/templates', - configPath: '/workspace/project/codebot.config.json', - isMultiProject: false - }; - - const deepPath = 'src/features/auth/components/forms/inputs/text/validation/rules'; - const resolvedPath = pathResolver.resolveRelativePath(deepContext, deepPath); - expect(resolvedPath).toBe('/workspace/project/src/features/auth/components/forms/inputs/text/validation/rules'); - }); - - it('should reject excessively long component names', () => { - const context: ProjectContext = { - workspaceRoot: '/workspace', - projectRoot: '/workspace/project', - templatePath: '/workspace/project/templates', - configPath: '/workspace/project/codebot.config.json', - isMultiProject: false - }; - - const veryLongName = 'Component' + 'A'.repeat(250); // 259 characters - expect(() => { - pathResolver.resolveTargetPath(context, veryLongName); - }).toThrow(); - }); - }); - - describe('Cross-platform compatibility', () => { - it('should normalize paths consistently across platforms', () => { - const mixedSeparatorContext: ProjectContext = { - workspaceRoot: '/workspace', - projectRoot: '/workspace/project', - templatePath: '/workspace/project/templates', - configPath: '/workspace/project/codebot.config.json', - isMultiProject: false - }; - - // Test mixed separators - const mixedPath = pathResolver.resolveRelativePath(mixedSeparatorContext, 'src\\components/forms'); - expect(mixedPath).toBe('/workspace/project/src/components/forms'); - }); - - it('should handle case sensitivity appropriately', () => { - const context: ProjectContext = { - workspaceRoot: '/workspace', - projectRoot: '/workspace/Project', - templatePath: '/workspace/Project/templates', - configPath: '/workspace/Project/codebot.config.json', - isMultiProject: false - }; - - const upperPath = pathResolver.resolveTargetPath(context, 'COMPONENT'); - const lowerPath = pathResolver.resolveTargetPath(context, 'component'); - - expect(upperPath).not.toBe(lowerPath); - expect(upperPath).toContain('COMPONENT'); - expect(lowerPath).toContain('component'); - }); - }); -}); \ No newline at end of file +import type { ProjectContext } from '../../../types'; +import { PathResolver } from '../../../utils/PathResolver'; + +describe('PathResolver Integration Tests', () => { + let pathResolver: PathResolver; + + beforeEach(() => { + pathResolver = new PathResolver(); + }); + + describe('Multi-project workspace scenarios', () => { + it('should resolve paths correctly for nested project structure', () => { + const nestedProjectContext: ProjectContext = { + workspaceRoot: '/workspace', + projectRoot: '/workspace/apps/frontend', + templatePath: '/workspace/apps/frontend/templates', + configPath: '/workspace/apps/frontend/codebot.config.json', + isMultiProject: true, + projectName: 'frontend', + }; + + const templatePath = pathResolver.resolveTemplatePath( + nestedProjectContext, + 'ReactComponent', + ); + expect(templatePath).toBe( + '/workspace/apps/frontend/templates/ReactComponent', + ); + + const targetPath = pathResolver.resolveTargetPath( + nestedProjectContext, + 'MyComponent', + ); + expect(targetPath).toBe('/workspace/apps/frontend/MyComponent'); + + const configPath = pathResolver.resolveConfigPath(nestedProjectContext); + expect(configPath).toBe('/workspace/apps/frontend/codebot.config.json'); + }); + + it('should resolve paths correctly for monorepo with packages', () => { + const packageProjectContext: ProjectContext = { + workspaceRoot: '/monorepo', + projectRoot: '/monorepo/packages/ui-components', + templatePath: '/monorepo/packages/ui-components/templates', + configPath: '/monorepo/packages/ui-components/codebot.config.json', + isMultiProject: true, + projectName: 'ui-components', + }; + + const templatePath = pathResolver.resolveTemplatePath( + packageProjectContext, + 'Component', + ); + expect(templatePath).toBe( + '/monorepo/packages/ui-components/templates/Component', + ); + + const relativePath = pathResolver.resolveRelativePath( + packageProjectContext, + 'src/components', + ); + expect(relativePath).toBe( + '/monorepo/packages/ui-components/src/components', + ); + }); + + it('should handle deeply nested project structures', () => { + const deepProjectContext: ProjectContext = { + workspaceRoot: '/company', + projectRoot: '/company/projects/web/apps/admin/modules/user-management', + templatePath: + '/company/projects/web/apps/admin/modules/user-management/templates', + configPath: + '/company/projects/web/apps/admin/modules/user-management/codebot.config.json', + isMultiProject: true, + projectName: 'user-management', + }; + + const targetPath = pathResolver.resolveTargetPath( + deepProjectContext, + 'UserProfile', + ); + expect(targetPath).toBe( + '/company/projects/web/apps/admin/modules/user-management/UserProfile', + ); + + // Ensure boundary validation works with deep paths + expect(() => { + pathResolver.resolveRelativePath( + deepProjectContext, + '../../../../../malicious', + ); + }).toThrow(); + }); + }); + + describe('Single project workspace scenarios', () => { + it('should resolve paths correctly for simple single project', () => { + const singleProjectContext: ProjectContext = { + workspaceRoot: '/simple-app', + projectRoot: '/simple-app', + templatePath: '/simple-app/templates', + configPath: '/simple-app/codebot.config.json', + isMultiProject: false, + }; + + const templatePath = pathResolver.resolveTemplatePath( + singleProjectContext, + 'Component', + ); + expect(templatePath).toBe('/simple-app/templates/Component'); + + const targetPath = pathResolver.resolveTargetPath( + singleProjectContext, + 'MyComponent', + ); + expect(targetPath).toBe('/simple-app/MyComponent'); + }); + + it('should handle single project with complex folder structure', () => { + const complexSingleProject: ProjectContext = { + workspaceRoot: '/complex-app', + projectRoot: '/complex-app', + templatePath: '/complex-app/templates', + configPath: '/complex-app/codebot.config.json', + isMultiProject: false, + }; + + const relativePath = pathResolver.resolveRelativePath( + complexSingleProject, + 'src/features/auth/components', + ); + expect(relativePath).toBe('/complex-app/src/features/auth/components'); + }); + }); + + describe('Edge cases and security', () => { + it('should prevent path traversal across project boundaries', () => { + const restrictedContext: ProjectContext = { + workspaceRoot: '/workspace', + projectRoot: '/workspace/project-a', + templatePath: '/workspace/project-a/templates', + configPath: '/workspace/project-a/codebot.config.json', + isMultiProject: true, + projectName: 'project-a', + }; + + // These should all throw errors due to path traversal attempts + expect(() => { + pathResolver.resolveRelativePath( + restrictedContext, + '../project-b/secrets', + ); + }).toThrow(); + + expect(() => { + pathResolver.resolveRelativePath(restrictedContext, '../../etc/passwd'); + }).toThrow(); + + expect(() => { + pathResolver.resolveTargetPath(restrictedContext, '../../../malicious'); + }).toThrow(); + }); + + it('should handle Windows-style paths in multi-project setup', () => { + const windowsContext: ProjectContext = { + workspaceRoot: 'C:\\workspace', + projectRoot: 'C:\\workspace\\project1', + templatePath: 'C:\\workspace\\project1\\templates', + configPath: 'C:\\workspace\\project1\\codebot.config.json', + isMultiProject: true, + projectName: 'project1', + }; + + const targetPath = pathResolver.resolveTargetPath( + windowsContext, + 'Component', + ); + expect(targetPath).toContain('Component'); + expect(targetPath).toContain('project1'); + }); + + it('should sanitize component names while preserving project structure', () => { + const projectContext: ProjectContext = { + workspaceRoot: '/workspace', + projectRoot: '/workspace/frontend', + templatePath: '/workspace/frontend/templates', + configPath: '/workspace/frontend/codebot.config.json', + isMultiProject: true, + projectName: 'frontend', + }; + + const targetPath = pathResolver.resolveTargetPath( + projectContext, + 'MyName', + ); + expect(targetPath).toBe('/workspace/frontend/My_Component_Name'); + + const templatePath = pathResolver.resolveTemplatePath( + projectContext, + 'React|Component', + ); + expect(templatePath).toBe( + '/workspace/frontend/templates/React_Component', + ); + }); + + it('should handle Unicode characters in project paths', () => { + const unicodeContext: ProjectContext = { + workspaceRoot: '/workspace', + projectRoot: '/workspace/项目-α', + templatePath: '/workspace/项目-α/templates', + configPath: '/workspace/项目-α/codebot.config.json', + isMultiProject: true, + projectName: '项目-α', + }; + + const targetPath = pathResolver.resolveTargetPath( + unicodeContext, + 'Component测试', + ); + expect(targetPath).toBe('/workspace/项目-α/Component测试'); + }); + }); + + describe('Performance and limits', () => { + it('should handle reasonable number of nested folders', () => { + const deepContext: ProjectContext = { + workspaceRoot: '/workspace', + projectRoot: '/workspace/project', + templatePath: '/workspace/project/templates', + configPath: '/workspace/project/codebot.config.json', + isMultiProject: false, + }; + + const deepPath = + 'src/features/auth/components/forms/inputs/text/validation/rules'; + const resolvedPath = pathResolver.resolveRelativePath( + deepContext, + deepPath, + ); + expect(resolvedPath).toBe( + '/workspace/project/src/features/auth/components/forms/inputs/text/validation/rules', + ); + }); + + it('should reject excessively long component names', () => { + const context: ProjectContext = { + workspaceRoot: '/workspace', + projectRoot: '/workspace/project', + templatePath: '/workspace/project/templates', + configPath: '/workspace/project/codebot.config.json', + isMultiProject: false, + }; + + const veryLongName = `Component${'A'.repeat(250)}`; // 259 characters + expect(() => { + pathResolver.resolveTargetPath(context, veryLongName); + }).toThrow(); + }); + }); + + describe('Cross-platform compatibility', () => { + it('should normalize paths consistently across platforms', () => { + const mixedSeparatorContext: ProjectContext = { + workspaceRoot: '/workspace', + projectRoot: '/workspace/project', + templatePath: '/workspace/project/templates', + configPath: '/workspace/project/codebot.config.json', + isMultiProject: false, + }; + + // Test mixed separators + const mixedPath = pathResolver.resolveRelativePath( + mixedSeparatorContext, + 'src\\components/forms', + ); + expect(mixedPath).toBe('/workspace/project/src/components/forms'); + }); + + it('should handle case sensitivity appropriately', () => { + const context: ProjectContext = { + workspaceRoot: '/workspace', + projectRoot: '/workspace/Project', + templatePath: '/workspace/Project/templates', + configPath: '/workspace/Project/codebot.config.json', + isMultiProject: false, + }; + + const upperPath = pathResolver.resolveTargetPath(context, 'COMPONENT'); + const lowerPath = pathResolver.resolveTargetPath(context, 'component'); + + expect(upperPath).not.toBe(lowerPath); + expect(upperPath).toContain('COMPONENT'); + expect(lowerPath).toContain('component'); + }); + }); +}); diff --git a/src/test/unit/utils/PathResolver.test.ts b/src/test/unit/utils/PathResolver.test.ts index 97a8f89..70ca8b2 100644 --- a/src/test/unit/utils/PathResolver.test.ts +++ b/src/test/unit/utils/PathResolver.test.ts @@ -1,303 +1,382 @@ -import { PathResolver } from '../../../utils/PathResolver'; -import { ProjectContext, ErrorType } from '../../../types'; -import { mockProjectContext, mockSingleProjectContext } from '../../fixtures'; - -describe('PathResolver', () => { - let pathResolver: PathResolver; - let mockMultiProjectContext: ProjectContext; - let mockSingleProjectContextLocal: ProjectContext; - - beforeEach(() => { - pathResolver = new PathResolver(); - - // Create test-specific contexts to avoid modifying shared fixtures - mockMultiProjectContext = { - ...mockProjectContext, - workspaceRoot: '/workspace', - projectRoot: '/workspace/project1', - templatePath: '/workspace/project1/templates', - configPath: '/workspace/project1/codebot.config.json', - isMultiProject: true, - projectName: 'project1' - }; - - mockSingleProjectContextLocal = { - ...mockSingleProjectContext, - workspaceRoot: '/workspace', - projectRoot: '/workspace', - templatePath: '/workspace/templates', - configPath: '/workspace/codebot.config.json', - isMultiProject: false - }; - }); - - describe('validatePath', () => { - it('should validate correct paths', () => { - expect(pathResolver.validatePath('component-name')).toBe(true); - expect(pathResolver.validatePath('ComponentName')).toBe(true); - expect(pathResolver.validatePath('component_name')).toBe(true); - expect(pathResolver.validatePath('folder/component')).toBe(true); - }); - - it('should reject paths with parent directory traversal', () => { - expect(pathResolver.validatePath('../component')).toBe(false); - expect(pathResolver.validatePath('folder/../component')).toBe(false); - expect(pathResolver.validatePath('../../malicious')).toBe(false); - }); - - it('should reject absolute paths', () => { - expect(pathResolver.validatePath('/absolute/path')).toBe(false); - expect(pathResolver.validatePath('C:\\Windows\\System32')).toBe(false); - }); - - it('should reject paths with invalid characters', () => { - expect(pathResolver.validatePath('componentname')).toBe(false); - expect(pathResolver.validatePath('component:name')).toBe(false); - expect(pathResolver.validatePath('component"name')).toBe(false); - expect(pathResolver.validatePath('component|name')).toBe(false); - expect(pathResolver.validatePath('component?name')).toBe(false); - expect(pathResolver.validatePath('component*name')).toBe(false); - }); - - it('should reject paths with null bytes', () => { - expect(pathResolver.validatePath('component\0name')).toBe(false); - }); - - it('should reject reserved Windows names', () => { - expect(pathResolver.validatePath('CON')).toBe(false); - expect(pathResolver.validatePath('PRN')).toBe(false); - expect(pathResolver.validatePath('AUX')).toBe(false); - expect(pathResolver.validatePath('NUL')).toBe(false); - expect(pathResolver.validatePath('COM1')).toBe(false); - expect(pathResolver.validatePath('LPT1')).toBe(false); - }); - - it('should reject empty or null paths', () => { - expect(pathResolver.validatePath('')).toBe(false); - expect(pathResolver.validatePath(null as any)).toBe(false); - expect(pathResolver.validatePath(undefined as any)).toBe(false); - }); - - it('should reject paths that are too long', () => { - const longPath = 'a'.repeat(300); - expect(pathResolver.validatePath(longPath)).toBe(false); - }); - - it('should reject paths with empty segments', () => { - expect(pathResolver.validatePath('folder//component')).toBe(false); - expect(pathResolver.validatePath('folder\\\\')).toBe(false); - }); - }); - - describe('sanitizePath', () => { - it('should sanitize dangerous characters', () => { - expect(pathResolver.sanitizePath('componentname')).toBe('component_name'); - expect(pathResolver.sanitizePath('component:name')).toBe('component_name'); - expect(pathResolver.sanitizePath('component"name')).toBe('component_name'); - expect(pathResolver.sanitizePath('component|name')).toBe('component_name'); - expect(pathResolver.sanitizePath('component?name')).toBe('component_name'); - expect(pathResolver.sanitizePath('component*name')).toBe('component_name'); - }); - - it('should remove null bytes', () => { - expect(pathResolver.sanitizePath('component\0name')).toBe('componentname'); - }); - - it('should remove parent directory references', () => { - expect(pathResolver.sanitizePath('../component')).toBe('component'); - expect(pathResolver.sanitizePath('folder/../component')).toBe('folder/component'); - expect(pathResolver.sanitizePath('../../malicious')).toBe('malicious'); - }); - - it('should normalize path separators', () => { - expect(pathResolver.sanitizePath('folder\\component')).toBe('folder/component'); - expect(pathResolver.sanitizePath('folder//component')).toBe('folder/component'); - }); - - it('should handle reserved names by appending underscore', () => { - expect(pathResolver.sanitizePath('CON')).toBe('CON_'); - expect(pathResolver.sanitizePath('PRN')).toBe('PRN_'); - expect(pathResolver.sanitizePath('folder/AUX')).toBe('folder/AUX_'); - }); - - it('should trim whitespace and dots', () => { - expect(pathResolver.sanitizePath(' component ')).toBe('component'); - expect(pathResolver.sanitizePath('..component..')).toBe('component'); - }); - - it('should handle empty or null input', () => { - expect(pathResolver.sanitizePath('')).toBe(''); - expect(pathResolver.sanitizePath(null as any)).toBe(''); - expect(pathResolver.sanitizePath(undefined as any)).toBe(''); - }); - }); - - describe('resolveTemplatePath', () => { - it('should resolve template path for multi-project workspace', () => { - const result = pathResolver.resolveTemplatePath(mockMultiProjectContext, 'ComponentSass'); - expect(result).toBe('/workspace/project1/templates/ComponentSass'); - }); - - it('should resolve template path for single project workspace', () => { - const result = pathResolver.resolveTemplatePath(mockSingleProjectContextLocal, 'ComponentSass'); - expect(result).toBe('/workspace/templates/ComponentSass'); - }); - - it('should sanitize template name', () => { - const result = pathResolver.resolveTemplatePath(mockMultiProjectContext, 'Component'); - expect(result).toBe('/workspace/project1/templates/Component_Sass_'); - }); - - it('should throw error for invalid template name', () => { - expect(() => { - pathResolver.resolveTemplatePath(mockMultiProjectContext, '../malicious'); - }).toThrow(); - }); - }); - - describe('resolveTargetPath', () => { - it('should resolve target path for multi-project workspace', () => { - const result = pathResolver.resolveTargetPath(mockMultiProjectContext, 'MyComponent'); - expect(result).toBe('/workspace/project1/MyComponent'); - }); - - it('should resolve target path for single project workspace', () => { - const result = pathResolver.resolveTargetPath(mockSingleProjectContextLocal, 'MyComponent'); - expect(result).toBe('/workspace/MyComponent'); - }); - - it('should sanitize component name', () => { - const result = pathResolver.resolveTargetPath(mockMultiProjectContext, 'My'); - expect(result).toBe('/workspace/project1/My_Component_'); - }); - - it('should throw error for invalid component name', () => { - expect(() => { - pathResolver.resolveTargetPath(mockMultiProjectContext, '../malicious'); - }).toThrow(); - }); - - it('should throw error if target path is outside project boundaries', () => { - // Mock a scenario where sanitized path could still be problematic - const maliciousContext = { - ...mockMultiProjectContext, - projectRoot: '/workspace/project1' - }; - - expect(() => { - // This should be caught by boundary validation - pathResolver.resolveTargetPath(maliciousContext, 'legitimate-name'); - }).not.toThrow(); // This should work fine - }); - }); - - describe('resolveConfigPath', () => { - it('should resolve config path for multi-project workspace', () => { - const result = pathResolver.resolveConfigPath(mockMultiProjectContext); - expect(result).toBe('/workspace/project1/codebot.config.json'); - }); - - it('should resolve config path for single project workspace', () => { - const result = pathResolver.resolveConfigPath(mockSingleProjectContextLocal); - expect(result).toBe('/workspace/codebot.config.json'); - }); - }); - - describe('resolveRelativePath', () => { - it('should resolve relative path within project boundaries', () => { - const result = pathResolver.resolveRelativePath(mockMultiProjectContext, 'src/components'); - expect(result).toBe('/workspace/project1/src/components'); - }); - - it('should sanitize relative path', () => { - const result = pathResolver.resolveRelativePath(mockMultiProjectContext, 'src/comp'); - expect(result).toBe('/workspace/project1/src/comp_onents_'); - }); - - it('should throw error for invalid relative path', () => { - expect(() => { - pathResolver.resolveRelativePath(mockMultiProjectContext, '../../../malicious'); - }).toThrow(); - }); - - it('should throw error if resolved path is outside boundaries', () => { - expect(() => { - pathResolver.resolveRelativePath(mockMultiProjectContext, '../outside-project'); - }).toThrow(); - }); - }); - - describe('edge cases and security', () => { - it('should handle Windows-style paths correctly', () => { - const windowsContext = { - ...mockMultiProjectContext, - workspaceRoot: 'C:\\workspace', - projectRoot: 'C:\\workspace\\project1' - }; - - const result = pathResolver.resolveTargetPath(windowsContext, 'MyComponent'); - expect(result).toContain('MyComponent'); - }); - - it('should handle very long component names', () => { - const longName = 'Component' + 'A'.repeat(200); - expect(() => { - pathResolver.resolveTargetPath(mockMultiProjectContext, longName); - }).toThrow(); - }); - - it('should handle special Unicode characters', () => { - const unicodeName = 'Component测试'; - const result = pathResolver.resolveTargetPath(mockMultiProjectContext, unicodeName); - expect(result).toContain('Component测试'); - }); - - it('should prevent path traversal in nested folders', () => { - expect(() => { - pathResolver.resolveRelativePath(mockMultiProjectContext, 'folder/../../../etc/passwd'); - }).toThrow(); - }); - - it('should handle case sensitivity correctly', () => { - const result1 = pathResolver.resolveTargetPath(mockMultiProjectContext, 'component'); - const result2 = pathResolver.resolveTargetPath(mockMultiProjectContext, 'Component'); - expect(result1).not.toBe(result2); - }); - }); - - describe('boundary validation', () => { - it('should allow paths within project root', () => { - const context = { - ...mockMultiProjectContext, - projectRoot: '/workspace/project1', - workspaceRoot: '/workspace' - }; - - expect(() => { - pathResolver.resolveTargetPath(context, 'ValidComponent'); - }).not.toThrow(); - }); - - it('should allow paths within workspace root for single project', () => { - expect(() => { - pathResolver.resolveTargetPath(mockSingleProjectContextLocal, 'ValidComponent'); - }).not.toThrow(); - }); - - it('should reject paths that escape project boundaries', () => { - // Create a context where we can test boundary violations - const restrictedContext = { - ...mockMultiProjectContext, - projectRoot: '/workspace/project1' - }; - - // The PathResolver should prevent any path that goes outside the project - // This is more of an integration test with the boundary checking logic - expect(() => { - pathResolver.resolveRelativePath(restrictedContext, '../../../etc'); - }).toThrow(); - }); - }); -}); \ No newline at end of file +import type { ProjectContext } from '../../../types'; +import { PathResolver } from '../../../utils/PathResolver'; +import { mockProjectContext, mockSingleProjectContext } from '../../fixtures'; + +describe('PathResolver', () => { + let pathResolver: PathResolver; + let mockMultiProjectContext: ProjectContext; + let mockSingleProjectContextLocal: ProjectContext; + + beforeEach(() => { + pathResolver = new PathResolver(); + + // Create test-specific contexts to avoid modifying shared fixtures + mockMultiProjectContext = { + ...mockProjectContext, + workspaceRoot: '/workspace', + projectRoot: '/workspace/project1', + templatePath: '/workspace/project1/templates', + configPath: '/workspace/project1/codebot.config.json', + isMultiProject: true, + projectName: 'project1', + }; + + mockSingleProjectContextLocal = { + ...mockSingleProjectContext, + workspaceRoot: '/workspace', + projectRoot: '/workspace', + templatePath: '/workspace/templates', + configPath: '/workspace/codebot.config.json', + isMultiProject: false, + }; + }); + + describe('validatePath', () => { + it('should validate correct paths', () => { + expect(pathResolver.validatePath('component-name')).toBe(true); + expect(pathResolver.validatePath('ComponentName')).toBe(true); + expect(pathResolver.validatePath('component_name')).toBe(true); + expect(pathResolver.validatePath('folder/component')).toBe(true); + }); + + it('should reject paths with parent directory traversal', () => { + expect(pathResolver.validatePath('../component')).toBe(false); + expect(pathResolver.validatePath('folder/../component')).toBe(false); + expect(pathResolver.validatePath('../../malicious')).toBe(false); + }); + + it('should reject absolute paths', () => { + expect(pathResolver.validatePath('/absolute/path')).toBe(false); + expect(pathResolver.validatePath('C:\\Windows\\System32')).toBe(false); + }); + + it('should reject paths with invalid characters', () => { + expect(pathResolver.validatePath('componentname')).toBe(false); + expect(pathResolver.validatePath('component:name')).toBe(false); + expect(pathResolver.validatePath('component"name')).toBe(false); + expect(pathResolver.validatePath('component|name')).toBe(false); + expect(pathResolver.validatePath('component?name')).toBe(false); + expect(pathResolver.validatePath('component*name')).toBe(false); + }); + + it('should reject paths with null bytes', () => { + expect(pathResolver.validatePath('component\0name')).toBe(false); + }); + + it('should reject reserved Windows names', () => { + expect(pathResolver.validatePath('CON')).toBe(false); + expect(pathResolver.validatePath('PRN')).toBe(false); + expect(pathResolver.validatePath('AUX')).toBe(false); + expect(pathResolver.validatePath('NUL')).toBe(false); + expect(pathResolver.validatePath('COM1')).toBe(false); + expect(pathResolver.validatePath('LPT1')).toBe(false); + }); + + it('should reject empty or null paths', () => { + expect(pathResolver.validatePath('')).toBe(false); + expect(pathResolver.validatePath(null as unknown as string)).toBe(false); + expect(pathResolver.validatePath(undefined as unknown as string)).toBe( + false, + ); + }); + + it('should reject paths that are too long', () => { + const longPath = 'a'.repeat(300); + expect(pathResolver.validatePath(longPath)).toBe(false); + }); + + it('should reject paths with empty segments', () => { + expect(pathResolver.validatePath('folder//component')).toBe(false); + expect(pathResolver.validatePath('folder\\\\')).toBe(false); + }); + }); + + describe('sanitizePath', () => { + it('should sanitize dangerous characters', () => { + expect(pathResolver.sanitizePath('componentname')).toBe( + 'component_name', + ); + expect(pathResolver.sanitizePath('component:name')).toBe( + 'component_name', + ); + expect(pathResolver.sanitizePath('component"name')).toBe( + 'component_name', + ); + expect(pathResolver.sanitizePath('component|name')).toBe( + 'component_name', + ); + expect(pathResolver.sanitizePath('component?name')).toBe( + 'component_name', + ); + expect(pathResolver.sanitizePath('component*name')).toBe( + 'component_name', + ); + }); + + it('should remove null bytes', () => { + expect(pathResolver.sanitizePath('component\0name')).toBe( + 'componentname', + ); + }); + + it('should remove parent directory references', () => { + expect(pathResolver.sanitizePath('../component')).toBe('component'); + expect(pathResolver.sanitizePath('folder/../component')).toBe( + 'folder/component', + ); + expect(pathResolver.sanitizePath('../../malicious')).toBe('malicious'); + }); + + it('should normalize path separators', () => { + expect(pathResolver.sanitizePath('folder\\component')).toBe( + 'folder/component', + ); + expect(pathResolver.sanitizePath('folder//component')).toBe( + 'folder/component', + ); + }); + + it('should handle reserved names by appending underscore', () => { + expect(pathResolver.sanitizePath('CON')).toBe('CON_'); + expect(pathResolver.sanitizePath('PRN')).toBe('PRN_'); + expect(pathResolver.sanitizePath('folder/AUX')).toBe('folder/AUX_'); + }); + + it('should trim whitespace and dots', () => { + expect(pathResolver.sanitizePath(' component ')).toBe('component'); + expect(pathResolver.sanitizePath('..component..')).toBe('component'); + }); + + it('should handle empty or null input', () => { + expect(pathResolver.sanitizePath('')).toBe(''); + expect(pathResolver.sanitizePath(null as unknown as string)).toBe(''); + expect(pathResolver.sanitizePath(undefined as unknown as string)).toBe( + '', + ); + }); + }); + + describe('resolveTemplatePath', () => { + it('should resolve template path for multi-project workspace', () => { + const result = pathResolver.resolveTemplatePath( + mockMultiProjectContext, + 'ComponentSass', + ); + expect(result).toBe('/workspace/project1/templates/ComponentSass'); + }); + + it('should resolve template path for single project workspace', () => { + const result = pathResolver.resolveTemplatePath( + mockSingleProjectContextLocal, + 'ComponentSass', + ); + expect(result).toBe('/workspace/templates/ComponentSass'); + }); + + it('should sanitize template name', () => { + const result = pathResolver.resolveTemplatePath( + mockMultiProjectContext, + 'Component', + ); + expect(result).toBe('/workspace/project1/templates/Component_Sass_'); + }); + + it('should throw error for invalid template name', () => { + expect(() => { + pathResolver.resolveTemplatePath( + mockMultiProjectContext, + '../malicious', + ); + }).toThrow(); + }); + }); + + describe('resolveTargetPath', () => { + it('should resolve target path for multi-project workspace', () => { + const result = pathResolver.resolveTargetPath( + mockMultiProjectContext, + 'MyComponent', + ); + expect(result).toBe('/workspace/project1/MyComponent'); + }); + + it('should resolve target path for single project workspace', () => { + const result = pathResolver.resolveTargetPath( + mockSingleProjectContextLocal, + 'MyComponent', + ); + expect(result).toBe('/workspace/MyComponent'); + }); + + it('should sanitize component name', () => { + const result = pathResolver.resolveTargetPath( + mockMultiProjectContext, + 'My', + ); + expect(result).toBe('/workspace/project1/My_Component_'); + }); + + it('should throw error for invalid component name', () => { + expect(() => { + pathResolver.resolveTargetPath(mockMultiProjectContext, '../malicious'); + }).toThrow(); + }); + + it('should throw error if target path is outside project boundaries', () => { + // Mock a scenario where sanitized path could still be problematic + const maliciousContext = { + ...mockMultiProjectContext, + projectRoot: '/workspace/project1', + }; + + expect(() => { + // This should be caught by boundary validation + pathResolver.resolveTargetPath(maliciousContext, 'legitimate-name'); + }).not.toThrow(); // This should work fine + }); + }); + + describe('resolveConfigPath', () => { + it('should resolve config path for multi-project workspace', () => { + const result = pathResolver.resolveConfigPath(mockMultiProjectContext); + expect(result).toBe('/workspace/project1/codebot.config.json'); + }); + + it('should resolve config path for single project workspace', () => { + const result = pathResolver.resolveConfigPath( + mockSingleProjectContextLocal, + ); + expect(result).toBe('/workspace/codebot.config.json'); + }); + }); + + describe('resolveRelativePath', () => { + it('should resolve relative path within project boundaries', () => { + const result = pathResolver.resolveRelativePath( + mockMultiProjectContext, + 'src/components', + ); + expect(result).toBe('/workspace/project1/src/components'); + }); + + it('should sanitize relative path', () => { + const result = pathResolver.resolveRelativePath( + mockMultiProjectContext, + 'src/comp', + ); + expect(result).toBe('/workspace/project1/src/comp_onents_'); + }); + + it('should throw error for invalid relative path', () => { + expect(() => { + pathResolver.resolveRelativePath( + mockMultiProjectContext, + '../../../malicious', + ); + }).toThrow(); + }); + + it('should throw error if resolved path is outside boundaries', () => { + expect(() => { + pathResolver.resolveRelativePath( + mockMultiProjectContext, + '../outside-project', + ); + }).toThrow(); + }); + }); + + describe('edge cases and security', () => { + it('should handle Windows-style paths correctly', () => { + const windowsContext = { + ...mockMultiProjectContext, + workspaceRoot: 'C:\\workspace', + projectRoot: 'C:\\workspace\\project1', + }; + + const result = pathResolver.resolveTargetPath( + windowsContext, + 'MyComponent', + ); + expect(result).toContain('MyComponent'); + }); + + it('should handle very long component names', () => { + const longName = `Component${'A'.repeat(200)}`; + expect(() => { + pathResolver.resolveTargetPath(mockMultiProjectContext, longName); + }).toThrow(); + }); + + it('should handle special Unicode characters', () => { + const unicodeName = 'Component测试'; + const result = pathResolver.resolveTargetPath( + mockMultiProjectContext, + unicodeName, + ); + expect(result).toContain('Component测试'); + }); + + it('should prevent path traversal in nested folders', () => { + expect(() => { + pathResolver.resolveRelativePath( + mockMultiProjectContext, + 'folder/../../../etc/passwd', + ); + }).toThrow(); + }); + + it('should handle case sensitivity correctly', () => { + const result1 = pathResolver.resolveTargetPath( + mockMultiProjectContext, + 'component', + ); + const result2 = pathResolver.resolveTargetPath( + mockMultiProjectContext, + 'Component', + ); + expect(result1).not.toBe(result2); + }); + }); + + describe('boundary validation', () => { + it('should allow paths within project root', () => { + const context = { + ...mockMultiProjectContext, + projectRoot: '/workspace/project1', + workspaceRoot: '/workspace', + }; + + expect(() => { + pathResolver.resolveTargetPath(context, 'ValidComponent'); + }).not.toThrow(); + }); + + it('should allow paths within workspace root for single project', () => { + expect(() => { + pathResolver.resolveTargetPath( + mockSingleProjectContextLocal, + 'ValidComponent', + ); + }).not.toThrow(); + }); + + it('should reject paths that escape project boundaries', () => { + // Create a context where we can test boundary violations + const restrictedContext = { + ...mockMultiProjectContext, + projectRoot: '/workspace/project1', + }; + + // The PathResolver should prevent any path that goes outside the project + // This is more of an integration test with the boundary checking logic + expect(() => { + pathResolver.resolveRelativePath(restrictedContext, '../../../etc'); + }).toThrow(); + }); + }); +}); diff --git a/src/test/utils/DirectoryProcessor.test.ts b/src/test/utils/DirectoryProcessor.test.ts index 02e2357..98b27bb 100644 --- a/src/test/utils/DirectoryProcessor.test.ts +++ b/src/test/utils/DirectoryProcessor.test.ts @@ -1,230 +1,244 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import * as os from 'os'; -import { DirectoryProcessor } from '../../utils/DirectoryProcessor'; -import { FileSystemManager } from '../../managers/FileSystemManager'; -import { TemplateStructure, ErrorType } from '../../types'; - -describe('DirectoryProcessor', () => { - let directoryProcessor: DirectoryProcessor; - let fileSystemManager: FileSystemManager; - let tempDir: string; - - beforeEach(() => { - fileSystemManager = new FileSystemManager(); - directoryProcessor = new DirectoryProcessor(fileSystemManager); - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codebot-dirproc-test-')); - }); - - afterEach(() => { - // Clean up temp directory - if (fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true, force: true }); - } - }); - - describe('scanTemplateStructure', () => { - beforeEach(async () => { - // Create complex directory structure - const structure = { - 'root-file1.hbs': 'root content 1', - 'root-file2.js': 'root content 2', - 'level1/file1.hbs': 'level1 content 1', - 'level1/file2.css': 'level1 content 2', - 'level1/level2/file3.hbs': 'level2 content 1', - 'level1/level2/level3/file4.html': 'level3 content 1', - 'another-level1/file5.hbs': 'another level1 content' - }; - - for (const [filePath, content] of Object.entries(structure)) { - const fullPath = path.join(tempDir, filePath); - await fileSystemManager.createFile(fullPath, content); - } - }); - - it('should scan complete directory structure', async () => { - const structure = await directoryProcessor.scanTemplateStructure(tempDir); - - expect(structure.rootPath).toBe(tempDir); - expect(structure.files).toHaveLength(2); // root-file1.hbs, root-file2.js - expect(structure.directories).toHaveLength(2); // level1, another-level1 - - // Check level1 directory - const level1Dir = structure.directories.find(d => d.name === 'level1'); - expect(level1Dir).toBeDefined(); - expect(level1Dir?.files).toHaveLength(2); // file1.hbs, file2.css - expect(level1Dir?.subdirectories).toHaveLength(1); // level2 - - // Check level2 directory - const level2Dir = level1Dir?.subdirectories.find(d => d.name === 'level2'); - expect(level2Dir).toBeDefined(); - expect(level2Dir?.files).toHaveLength(1); // file3.hbs - expect(level2Dir?.subdirectories).toHaveLength(1); // level3 - - // Check level3 directory - const level3Dir = level2Dir?.subdirectories.find(d => d.name === 'level3'); - expect(level3Dir).toBeDefined(); - expect(level3Dir?.files).toHaveLength(1); // file4.html - expect(level3Dir?.subdirectories).toHaveLength(0); - }); - - it('should set correct relative paths', async () => { - const structure = await directoryProcessor.scanTemplateStructure(tempDir); - - // Check root files - const rootFile = structure.files.find(f => f.name === 'root-file1.hbs'); - expect(rootFile?.relativePath).toBe('root-file1.hbs'); - - // Check nested directory - const level1Dir = structure.directories.find(d => d.name === 'level1'); - expect(level1Dir?.relativePath).toBe('level1'); - - // Check nested file - const nestedFile = level1Dir?.files.find(f => f.name === 'file1.hbs'); - expect(nestedFile?.relativePath).toBe('file1.hbs'); - }); - - it('should handle empty directories', async () => { - const emptyDir = path.join(tempDir, 'empty'); - await fileSystemManager.createFolderRecursive(emptyDir); - - const structure = await directoryProcessor.scanTemplateStructure(tempDir); - - const emptyDirEntry = structure.directories.find(d => d.name === 'empty'); - expect(emptyDirEntry).toBeDefined(); - expect(emptyDirEntry?.files).toHaveLength(0); - expect(emptyDirEntry?.subdirectories).toHaveLength(0); - }); - - it('should throw error for non-existent directory', async () => { - const nonExistentDir = path.join(tempDir, 'nonexistent'); - - await expect(directoryProcessor.scanTemplateStructure(nonExistentDir)) - .rejects.toMatchObject({ - type: ErrorType.TEMPLATE_PROCESSING_ERROR, - message: expect.stringContaining('Template directory does not exist') - }); - }); - }); - - describe('validateDepth', () => { - it('should pass validation for normal depth', async () => { - // Create structure with depth 3 - const structure = { - 'level1/level2/level3/file.hbs': 'content' - }; - - for (const [filePath, content] of Object.entries(structure)) { - const fullPath = path.join(tempDir, filePath); - await fileSystemManager.createFile(fullPath, content); - } - - const templateStructure = await directoryProcessor.scanTemplateStructure(tempDir); - - expect(() => directoryProcessor.validateDepth(templateStructure)).not.toThrow(); - }); - - it('should throw error when maximum depth is exceeded', async () => { - // Create structure with depth > 15 - let deepPath = tempDir; - for (let i = 0; i < 17; i++) { - deepPath = path.join(deepPath, `level${i}`); - } - await fileSystemManager.createFile(path.join(deepPath, 'file.hbs'), 'content'); - - // The error should be thrown during scanning, not validation - await expect(directoryProcessor.scanTemplateStructure(tempDir)) - .rejects.toMatchObject({ - type: ErrorType.FILE_SYSTEM_ERROR, - message: expect.stringContaining('Maximum directory depth exceeded') - }); - }); - }); - - describe('file extension handling', () => { - beforeEach(async () => { - const structure = { - 'template.hbs': 'handlebars template', - 'style.css': 'css content', - 'script.js': 'js content', - 'config.json': 'json content', - 'readme.md': 'markdown content' - }; - - for (const [filePath, content] of Object.entries(structure)) { - const fullPath = path.join(tempDir, filePath); - await fileSystemManager.createFile(fullPath, content); - } - }); - - it('should correctly identify file extensions', async () => { - const structure = await directoryProcessor.scanTemplateStructure(tempDir); - - const hbsFile = structure.files.find(f => f.name === 'template.hbs'); - expect(hbsFile?.extension).toBe('.hbs'); - - const cssFile = structure.files.find(f => f.name === 'style.css'); - expect(cssFile?.extension).toBe('.css'); - - const jsFile = structure.files.find(f => f.name === 'script.js'); - expect(jsFile?.extension).toBe('.js'); - - const jsonFile = structure.files.find(f => f.name === 'config.json'); - expect(jsonFile?.extension).toBe('.json'); - - const mdFile = structure.files.find(f => f.name === 'readme.md'); - expect(mdFile?.extension).toBe('.md'); - }); - }); - - describe('complex nested structures', () => { - beforeEach(async () => { - // Create a realistic component template structure - const structure = { - 'index.hbs': 'export { default } from "./{{name}}";', - '{{name}}.hbs': 'import React from "react";', - '{{name}}.test.hbs': 'import { render } from "@testing-library/react";', - 'styles/index.css.hbs': '@import "./{{name}}.css";', - 'styles/{{name}}.css.hbs': '.{{name}} { display: block; }', - 'styles/themes/light.css.hbs': '.{{name}}.light { background: white; }', - 'styles/themes/dark.css.hbs': '.{{name}}.dark { background: black; }', - 'utils/helpers.js.hbs': 'export const {{name}}Helper = () => {};', - 'utils/constants.js.hbs': 'export const {{name}}_CONSTANTS = {};', - 'assets/icons/icon.svg.hbs': '{{name}}', - 'docs/README.md.hbs': '# {{name}} Component' - }; - - for (const [filePath, content] of Object.entries(structure)) { - const fullPath = path.join(tempDir, filePath); - await fileSystemManager.createFile(fullPath, content); - } - }); - - it('should handle complex nested component structure', async () => { - const structure = await directoryProcessor.scanTemplateStructure(tempDir); - - expect(structure.files).toHaveLength(3); // index.hbs, {{name}}.hbs, {{name}}.test.hbs - expect(structure.directories).toHaveLength(4); // styles, utils, assets, docs - - // Check styles directory - const stylesDir = structure.directories.find(d => d.name === 'styles'); - expect(stylesDir?.files).toHaveLength(2); // index.css.hbs, {{name}}.css.hbs - expect(stylesDir?.subdirectories).toHaveLength(1); // themes - - // Check themes subdirectory - const themesDir = stylesDir?.subdirectories.find(d => d.name === 'themes'); - expect(themesDir?.files).toHaveLength(2); // light.css.hbs, dark.css.hbs - - // Check utils directory - const utilsDir = structure.directories.find(d => d.name === 'utils'); - expect(utilsDir?.files).toHaveLength(2); // helpers.js.hbs, constants.js.hbs - - // Check assets directory with nested icons - const assetsDir = structure.directories.find(d => d.name === 'assets'); - expect(assetsDir?.subdirectories).toHaveLength(1); // icons - - const iconsDir = assetsDir?.subdirectories.find(d => d.name === 'icons'); - expect(iconsDir?.files).toHaveLength(1); // icon.svg.hbs - }); - }); -}); \ No newline at end of file +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import { FileSystemManager } from '../../managers/FileSystemManager'; +import { ErrorType } from '../../types'; +import { DirectoryProcessor } from '../../utils/DirectoryProcessor'; + +describe('DirectoryProcessor', () => { + let directoryProcessor: DirectoryProcessor; + let fileSystemManager: FileSystemManager; + let tempDir: string; + + beforeEach(() => { + fileSystemManager = new FileSystemManager(); + directoryProcessor = new DirectoryProcessor(fileSystemManager); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codebot-dirproc-test-')); + }); + + afterEach(() => { + // Clean up temp directory + if (fs.existsSync(tempDir)) { + fs.rmSync(tempDir, { recursive: true, force: true }); + } + }); + + describe('scanTemplateStructure', () => { + beforeEach(async () => { + // Create complex directory structure + const structure = { + 'root-file1.hbs': 'root content 1', + 'root-file2.js': 'root content 2', + 'level1/file1.hbs': 'level1 content 1', + 'level1/file2.css': 'level1 content 2', + 'level1/level2/file3.hbs': 'level2 content 1', + 'level1/level2/level3/file4.html': 'level3 content 1', + 'another-level1/file5.hbs': 'another level1 content', + }; + + for (const [filePath, content] of Object.entries(structure)) { + const fullPath = path.join(tempDir, filePath); + await fileSystemManager.createFile(fullPath, content); + } + }); + + it('should scan complete directory structure', async () => { + const structure = await directoryProcessor.scanTemplateStructure(tempDir); + + expect(structure.rootPath).toBe(tempDir); + expect(structure.files).toHaveLength(2); // root-file1.hbs, root-file2.js + expect(structure.directories).toHaveLength(2); // level1, another-level1 + + // Check level1 directory + const level1Dir = structure.directories.find(d => d.name === 'level1'); + expect(level1Dir).toBeDefined(); + expect(level1Dir?.files).toHaveLength(2); // file1.hbs, file2.css + expect(level1Dir?.subdirectories).toHaveLength(1); // level2 + + // Check level2 directory + const level2Dir = level1Dir?.subdirectories.find( + d => d.name === 'level2', + ); + expect(level2Dir).toBeDefined(); + expect(level2Dir?.files).toHaveLength(1); // file3.hbs + expect(level2Dir?.subdirectories).toHaveLength(1); // level3 + + // Check level3 directory + const level3Dir = level2Dir?.subdirectories.find( + d => d.name === 'level3', + ); + expect(level3Dir).toBeDefined(); + expect(level3Dir?.files).toHaveLength(1); // file4.html + expect(level3Dir?.subdirectories).toHaveLength(0); + }); + + it('should set correct relative paths', async () => { + const structure = await directoryProcessor.scanTemplateStructure(tempDir); + + // Check root files + const rootFile = structure.files.find(f => f.name === 'root-file1.hbs'); + expect(rootFile?.relativePath).toBe('root-file1.hbs'); + + // Check nested directory + const level1Dir = structure.directories.find(d => d.name === 'level1'); + expect(level1Dir?.relativePath).toBe('level1'); + + // Check nested file + const nestedFile = level1Dir?.files.find(f => f.name === 'file1.hbs'); + expect(nestedFile?.relativePath).toBe('file1.hbs'); + }); + + it('should handle empty directories', async () => { + const emptyDir = path.join(tempDir, 'empty'); + await fileSystemManager.createFolderRecursive(emptyDir); + + const structure = await directoryProcessor.scanTemplateStructure(tempDir); + + const emptyDirEntry = structure.directories.find(d => d.name === 'empty'); + expect(emptyDirEntry).toBeDefined(); + expect(emptyDirEntry?.files).toHaveLength(0); + expect(emptyDirEntry?.subdirectories).toHaveLength(0); + }); + + it('should throw error for non-existent directory', async () => { + const nonExistentDir = path.join(tempDir, 'nonexistent'); + + await expect( + directoryProcessor.scanTemplateStructure(nonExistentDir), + ).rejects.toMatchObject({ + type: ErrorType.TEMPLATE_PROCESSING_ERROR, + message: expect.stringContaining('Template directory does not exist'), + }); + }); + }); + + describe('validateDepth', () => { + it('should pass validation for normal depth', async () => { + // Create structure with depth 3 + const structure = { + 'level1/level2/level3/file.hbs': 'content', + }; + + for (const [filePath, content] of Object.entries(structure)) { + const fullPath = path.join(tempDir, filePath); + await fileSystemManager.createFile(fullPath, content); + } + + const templateStructure = + await directoryProcessor.scanTemplateStructure(tempDir); + + expect(() => + directoryProcessor.validateDepth(templateStructure), + ).not.toThrow(); + }); + + it('should throw error when maximum depth is exceeded', async () => { + // Create structure with depth > 15 + let deepPath = tempDir; + for (let i = 0; i < 17; i++) { + deepPath = path.join(deepPath, `level${i}`); + } + await fileSystemManager.createFile( + path.join(deepPath, 'file.hbs'), + 'content', + ); + + // The error should be thrown during scanning, not validation + await expect( + directoryProcessor.scanTemplateStructure(tempDir), + ).rejects.toMatchObject({ + type: ErrorType.FILE_SYSTEM_ERROR, + message: expect.stringContaining('Maximum directory depth exceeded'), + }); + }); + }); + + describe('file extension handling', () => { + beforeEach(async () => { + const structure = { + 'template.hbs': 'handlebars template', + 'style.css': 'css content', + 'script.js': 'js content', + 'config.json': 'json content', + 'readme.md': 'markdown content', + }; + + for (const [filePath, content] of Object.entries(structure)) { + const fullPath = path.join(tempDir, filePath); + await fileSystemManager.createFile(fullPath, content); + } + }); + + it('should correctly identify file extensions', async () => { + const structure = await directoryProcessor.scanTemplateStructure(tempDir); + + const hbsFile = structure.files.find(f => f.name === 'template.hbs'); + expect(hbsFile?.extension).toBe('.hbs'); + + const cssFile = structure.files.find(f => f.name === 'style.css'); + expect(cssFile?.extension).toBe('.css'); + + const jsFile = structure.files.find(f => f.name === 'script.js'); + expect(jsFile?.extension).toBe('.js'); + + const jsonFile = structure.files.find(f => f.name === 'config.json'); + expect(jsonFile?.extension).toBe('.json'); + + const mdFile = structure.files.find(f => f.name === 'readme.md'); + expect(mdFile?.extension).toBe('.md'); + }); + }); + + describe('complex nested structures', () => { + beforeEach(async () => { + // Create a realistic component template structure + const structure = { + 'index.hbs': 'export { default } from "./{{name}}";', + '{{name}}.hbs': 'import React from "react";', + '{{name}}.test.hbs': 'import { render } from "@testing-library/react";', + 'styles/index.css.hbs': '@import "./{{name}}.css";', + 'styles/{{name}}.css.hbs': '.{{name}} { display: block; }', + 'styles/themes/light.css.hbs': '.{{name}}.light { background: white; }', + 'styles/themes/dark.css.hbs': '.{{name}}.dark { background: black; }', + 'utils/helpers.js.hbs': 'export const {{name}}Helper = () => {};', + 'utils/constants.js.hbs': 'export const {{name}}_CONSTANTS = {};', + 'assets/icons/icon.svg.hbs': '{{name}}', + 'docs/README.md.hbs': '# {{name}} Component', + }; + + for (const [filePath, content] of Object.entries(structure)) { + const fullPath = path.join(tempDir, filePath); + await fileSystemManager.createFile(fullPath, content); + } + }); + + it('should handle complex nested component structure', async () => { + const structure = await directoryProcessor.scanTemplateStructure(tempDir); + + expect(structure.files).toHaveLength(3); // index.hbs, {{name}}.hbs, {{name}}.test.hbs + expect(structure.directories).toHaveLength(4); // styles, utils, assets, docs + + // Check styles directory + const stylesDir = structure.directories.find(d => d.name === 'styles'); + expect(stylesDir?.files).toHaveLength(2); // index.css.hbs, {{name}}.css.hbs + expect(stylesDir?.subdirectories).toHaveLength(1); // themes + + // Check themes subdirectory + const themesDir = stylesDir?.subdirectories.find( + d => d.name === 'themes', + ); + expect(themesDir?.files).toHaveLength(2); // light.css.hbs, dark.css.hbs + + // Check utils directory + const utilsDir = structure.directories.find(d => d.name === 'utils'); + expect(utilsDir?.files).toHaveLength(2); // helpers.js.hbs, constants.js.hbs + + // Check assets directory with nested icons + const assetsDir = structure.directories.find(d => d.name === 'assets'); + expect(assetsDir?.subdirectories).toHaveLength(1); // icons + + const iconsDir = assetsDir?.subdirectories.find(d => d.name === 'icons'); + expect(iconsDir?.files).toHaveLength(1); // icon.svg.hbs + }); + }); +}); diff --git a/src/types/index.ts b/src/types/index.ts index 927b2b3..ea73ccf 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -48,7 +48,7 @@ export interface GenerationContext { componentName: string; targetPath: string; templateType: string; - variables: Record; + variables: Record; options: GenerationOptions; } @@ -114,13 +114,13 @@ export enum ErrorType { FILE_SYSTEM_ERROR = 'FILE_SYSTEM_ERROR', TEMPLATE_PROCESSING_ERROR = 'TEMPLATE_PROCESSING_ERROR', PROJECT_DETECTION_ERROR = 'PROJECT_DETECTION_ERROR', - CONFIGURATION_ERROR = 'CONFIGURATION_ERROR' + CONFIGURATION_ERROR = 'CONFIGURATION_ERROR', } export interface CodebotError { type: ErrorType; message: string; - details?: any; + details?: unknown; recoverable: boolean; } @@ -128,8 +128,8 @@ export interface CommandArgs { fsPath?: string; } +export * from '../constants'; +export * from '../errors'; // Re-export interfaces and utilities for convenience export * from '../interfaces'; -export * from '../errors'; -export * from '../constants'; export * from '../utils'; diff --git a/src/utils/DirectoryProcessor.ts b/src/utils/DirectoryProcessor.ts index 6ae2285..568f099 100644 --- a/src/utils/DirectoryProcessor.ts +++ b/src/utils/DirectoryProcessor.ts @@ -1,106 +1,135 @@ -import * as path from 'path'; -import { FileSystemManager } from '../managers/FileSystemManager'; -import { TemplateStructure, TemplateFileInfo, TemplateDirectoryInfo, FileSystemEntry, ErrorType, CodebotError } from '../types'; - -export class DirectoryProcessor { - private static readonly MAX_DEPTH = 15; // Security limit for directory depth - private fileSystemManager: FileSystemManager; - - constructor(fileSystemManager?: FileSystemManager) { - this.fileSystemManager = fileSystemManager || new FileSystemManager(); - } - - async scanTemplateStructure(templatePath: string): Promise { - try { - if (!await this.fileSystemManager.folderExists(templatePath)) { - throw this.createError(`Template directory does not exist: ${templatePath}`); - } - - const entries = await this.fileSystemManager.listFilesRecursive(templatePath); - return this.buildTemplateStructure(templatePath, entries); - } catch (error) { - if (error && typeof error === 'object' && 'type' in error) { - throw error; // Re-throw CodebotError instances - } - throw this.createError(`Failed to scan template structure: ${templatePath}`, error); - } - } - - private buildTemplateStructure(rootPath: string, entries: FileSystemEntry[]): TemplateStructure { - const files: TemplateFileInfo[] = []; - const directories: TemplateDirectoryInfo[] = []; - - for (const entry of entries) { - if (entry.isDirectory) { - const directoryInfo = this.buildDirectoryInfo(rootPath, entry); - directories.push(directoryInfo); - } else { - const fileInfo = this.buildFileInfo(rootPath, entry); - files.push(fileInfo); - } - } - - return { - rootPath, - files, - directories - }; - } - - private buildFileInfo(rootPath: string, entry: FileSystemEntry): TemplateFileInfo { - return { - name: entry.name, - path: entry.path, - relativePath: entry.relativePath, - extension: path.extname(entry.name) - }; - } - - private buildDirectoryInfo(rootPath: string, entry: FileSystemEntry): TemplateDirectoryInfo { - const files: TemplateFileInfo[] = []; - const subdirectories: TemplateDirectoryInfo[] = []; - - if (entry.children) { - for (const child of entry.children) { - if (child.isDirectory) { - const subDirInfo = this.buildDirectoryInfo(rootPath, child); - subdirectories.push(subDirInfo); - } else { - const fileInfo = this.buildFileInfo(rootPath, child); - files.push(fileInfo); - } - } - } - - return { - name: entry.name, - path: entry.path, - relativePath: entry.relativePath, - files, - subdirectories - }; - } - - validateDepth(structure: TemplateStructure): void { - this.validateDirectoryDepth(structure.directories, 0); - } - - private validateDirectoryDepth(directories: TemplateDirectoryInfo[], currentDepth: number): void { - if (currentDepth > DirectoryProcessor.MAX_DEPTH) { - throw this.createError(`Maximum directory depth exceeded: ${DirectoryProcessor.MAX_DEPTH}`); - } - - for (const directory of directories) { - this.validateDirectoryDepth(directory.subdirectories, currentDepth + 1); - } - } - - private createError(message: string, originalError?: any): CodebotError { - return { - type: ErrorType.TEMPLATE_PROCESSING_ERROR, - message, - details: originalError, - recoverable: false - }; - } -} \ No newline at end of file +import * as path from 'node:path'; +import { FileSystemManager } from '../managers/FileSystemManager'; +import { + type CodebotError, + ErrorType, + type FileSystemEntry, + type TemplateDirectoryInfo, + type TemplateFileInfo, + type TemplateStructure, +} from '../types'; + +export class DirectoryProcessor { + private static readonly MAX_DEPTH = 15; // Security limit for directory depth + private fileSystemManager: FileSystemManager; + + constructor(fileSystemManager?: FileSystemManager) { + this.fileSystemManager = fileSystemManager || new FileSystemManager(); + } + + async scanTemplateStructure( + templatePath: string, + ): Promise { + try { + if (!(await this.fileSystemManager.folderExists(templatePath))) { + throw this.createError( + `Template directory does not exist: ${templatePath}`, + ); + } + + const entries = + await this.fileSystemManager.listFilesRecursive(templatePath); + return this.buildTemplateStructure(templatePath, entries); + } catch (error) { + if (error && typeof error === 'object' && 'type' in error) { + throw error; // Re-throw CodebotError instances + } + throw this.createError( + `Failed to scan template structure: ${templatePath}`, + error, + ); + } + } + + private buildTemplateStructure( + rootPath: string, + entries: FileSystemEntry[], + ): TemplateStructure { + const files: TemplateFileInfo[] = []; + const directories: TemplateDirectoryInfo[] = []; + + for (const entry of entries) { + if (entry.isDirectory) { + const directoryInfo = this.buildDirectoryInfo(rootPath, entry); + directories.push(directoryInfo); + } else { + const fileInfo = this.buildFileInfo(rootPath, entry); + files.push(fileInfo); + } + } + + return { + rootPath, + files, + directories, + }; + } + + private buildFileInfo( + _rootPath: string, + entry: FileSystemEntry, + ): TemplateFileInfo { + return { + name: entry.name, + path: entry.path, + relativePath: entry.relativePath, + extension: path.extname(entry.name), + }; + } + + private buildDirectoryInfo( + rootPath: string, + entry: FileSystemEntry, + ): TemplateDirectoryInfo { + const files: TemplateFileInfo[] = []; + const subdirectories: TemplateDirectoryInfo[] = []; + + if (entry.children) { + for (const child of entry.children) { + if (child.isDirectory) { + const subDirInfo = this.buildDirectoryInfo(rootPath, child); + subdirectories.push(subDirInfo); + } else { + const fileInfo = this.buildFileInfo(rootPath, child); + files.push(fileInfo); + } + } + } + + return { + name: entry.name, + path: entry.path, + relativePath: entry.relativePath, + files, + subdirectories, + }; + } + + validateDepth(structure: TemplateStructure): void { + this.validateDirectoryDepth(structure.directories, 0); + } + + private validateDirectoryDepth( + directories: TemplateDirectoryInfo[], + currentDepth: number, + ): void { + if (currentDepth > DirectoryProcessor.MAX_DEPTH) { + throw this.createError( + `Maximum directory depth exceeded: ${DirectoryProcessor.MAX_DEPTH}`, + ); + } + + for (const directory of directories) { + this.validateDirectoryDepth(directory.subdirectories, currentDepth + 1); + } + } + + private createError(message: string, originalError?: unknown): CodebotError { + return { + type: ErrorType.TEMPLATE_PROCESSING_ERROR, + message, + details: originalError, + recoverable: false, + }; + } +} diff --git a/src/utils/PathResolver.ts b/src/utils/PathResolver.ts index 3cd06d9..ac02d11 100644 --- a/src/utils/PathResolver.ts +++ b/src/utils/PathResolver.ts @@ -1,283 +1,326 @@ -import * as path from 'path'; -import { IPathResolver, ProjectContext, ErrorType } from '../types'; -import { CodebotError } from '../errors'; - -export class PathResolver implements IPathResolver { - private static readonly FORBIDDEN_PATTERNS = [ - /\.\./, // Parent directory traversal - /^\/+/, // Absolute paths starting with / - /^[a-zA-Z]:\\/, // Windows absolute paths - /\0/, // Null bytes - /[<>:"|?*]/, // Invalid filename characters - ]; - - private static readonly MAX_PATH_LENGTH = 260; // Windows MAX_PATH limitation - private static readonly MAX_COMPONENT_NAME_LENGTH = 200; // Reasonable component name limit - private static readonly RESERVED_NAMES = [ - 'CON', 'PRN', 'AUX', 'NUL', - 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', - 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9' - ]; - - /** - * Resolves the template path based on project context - * Follows hierarchy: project-specific templates → workspace templates → fallback - */ - resolveTemplatePath(projectContext: ProjectContext, templateName: string): string { - // Check for dangerous security patterns first - if (!this.validatePathSecurity(templateName)) { - throw new CodebotError( - ErrorType.FILE_SYSTEM_ERROR, - `Invalid template name: ${templateName}`, - { templateName }, - false - ); - } - - const sanitizedTemplateName = this.sanitizePath(templateName); - - // For multi-project workspaces, prefer project-specific templates - if (projectContext.isMultiProject) { - const projectTemplatePath = path.join(projectContext.projectRoot, 'templates', sanitizedTemplateName); - return this.normalizePath(projectTemplatePath); - } - - // For single project or fallback, use workspace-level templates - const workspaceTemplatePath = path.join(projectContext.workspaceRoot, 'templates', sanitizedTemplateName); - return this.normalizePath(workspaceTemplatePath); - } - - /** - * Resolves the target path where component will be created - * Ensures path is within project boundaries - */ - resolveTargetPath(projectContext: ProjectContext, componentName: string, selectedFolderPath?: string): string { - // Check for dangerous security patterns first - if (!this.validatePathSecurity(componentName)) { - throw new CodebotError( - ErrorType.INVALID_COMPONENT_NAME, - `Invalid component name: ${componentName}`, - { componentName }, - true - ); - } - - const sanitizedComponentName = this.sanitizePath(componentName); - - // Determine the base path for component creation - const basePath = this.resolveBasePath(projectContext, selectedFolderPath); - const targetPath = path.join(basePath, sanitizedComponentName); - - // Ensure the resolved path is within project boundaries - if (!this.isWithinProjectBoundaries(targetPath, projectContext)) { - throw new CodebotError( - ErrorType.FILE_SYSTEM_ERROR, - `Target path is outside project boundaries: ${targetPath}`, - { targetPath, projectRoot: projectContext.projectRoot }, - false - ); - } - - return this.normalizePath(targetPath); - } - - /** - * Validates a path for security and correctness (strict validation) - */ - validatePath(inputPath: string): boolean { - if (!inputPath || typeof inputPath !== 'string') { - return false; - } - - // Check path length - if (inputPath.length > PathResolver.MAX_PATH_LENGTH) { - return false; - } - - // Check for forbidden patterns - for (const pattern of PathResolver.FORBIDDEN_PATTERNS) { - if (pattern.test(inputPath)) { - return false; - } - } - - // Check for reserved names (Windows) - const pathParts = inputPath.split(/[/\\]/); - for (const part of pathParts) { - const upperPart = part.toUpperCase(); - if (PathResolver.RESERVED_NAMES.includes(upperPart)) { - return false; - } - } - - // Check for empty path segments - if (pathParts.some(part => part === '')) { - return false; - } - - return true; - } - - /** - * Validates a path strictly for dangerous security patterns only - */ - private validatePathSecurity(inputPath: string): boolean { - if (!inputPath || typeof inputPath !== 'string') { - return false; - } - - // Check component name length (reasonable limit) - if (inputPath.length > PathResolver.MAX_COMPONENT_NAME_LENGTH) { - return false; - } - - // Check for dangerous patterns only - const dangerousPatterns = [ - /\.\./, // Parent directory traversal - /^\/+/, // Absolute paths starting with / - /^[a-zA-Z]:\\/, // Windows absolute paths - /\0/, // Null bytes - ]; - - for (const pattern of dangerousPatterns) { - if (pattern.test(inputPath)) { - return false; - } - } - - return true; - } - - /** - * Sanitizes a path by removing dangerous characters and patterns - */ - sanitizePath(inputPath: string): string { - if (!inputPath || typeof inputPath !== 'string') { - return ''; - } - - let sanitized = inputPath - // Remove null bytes - .replace(/\0/g, '') - // Remove or replace invalid characters - .replace(/[<>:"|?*]/g, '_') - // Remove leading/trailing whitespace and dots - .trim() - .replace(/^\.+|\.+$/g, '') - // Normalize path separators - .replace(/[/\\]+/g, path.sep) - // Remove parent directory references - .replace(/\.\.[/\\]/g, '') - .replace(/\.\.$/, ''); - - // Ensure it doesn't start with a path separator - sanitized = sanitized.replace(/^[/\\]+/, ''); - - // Handle reserved names by appending underscore - const pathParts = sanitized.split(path.sep); - const sanitizedParts = pathParts.map(part => { - const upperPart = part.toUpperCase(); - if (PathResolver.RESERVED_NAMES.includes(upperPart)) { - return part + '_'; - } - return part; - }); - - return sanitizedParts.join(path.sep); - } - - /** - * Resolves the configuration file path for a project - */ - resolveConfigPath(projectContext: ProjectContext): string { - // For multi-project, prefer project-specific config - if (projectContext.isMultiProject) { - const projectConfigPath = path.join(projectContext.projectRoot, 'codebot.config.json'); - return this.normalizePath(projectConfigPath); - } - - // For single project, use workspace-level config - const workspaceConfigPath = path.join(projectContext.workspaceRoot, 'codebot.config.json'); - return this.normalizePath(workspaceConfigPath); - } - - /** - * Resolves relative paths to absolute paths within project context - */ - resolveRelativePath(projectContext: ProjectContext, relativePath: string): string { - // Check for dangerous security patterns first - if (!this.validatePathSecurity(relativePath)) { - throw new CodebotError( - ErrorType.FILE_SYSTEM_ERROR, - `Invalid relative path: ${relativePath}`, - { relativePath }, - false - ); - } - - const sanitizedPath = this.sanitizePath(relativePath); - - const basePath = this.resolveBasePath(projectContext); - const absolutePath = path.resolve(basePath, sanitizedPath); - - // Ensure the resolved path is within project boundaries - if (!this.isWithinProjectBoundaries(absolutePath, projectContext)) { - throw new CodebotError( - ErrorType.FILE_SYSTEM_ERROR, - `Resolved path is outside project boundaries: ${absolutePath}`, - { relativePath, absolutePath, projectRoot: projectContext.projectRoot }, - false - ); - } - - return this.normalizePath(absolutePath); - } - - /** - * Determines the base path for operations based on project context - */ - private resolveBasePath(projectContext: ProjectContext, selectedFolderPath?: string): string { - // If a specific folder was selected, use that as the base path - if (selectedFolderPath) { - // Ensure the selected folder is within project boundaries - if (this.isWithinProjectBoundaries(selectedFolderPath, projectContext)) { - return selectedFolderPath; - } - } - - // Fallback to project root - return projectContext.projectRoot; - } - - /** - * Checks if a path is within the allowed project boundaries - */ - private isWithinProjectBoundaries(targetPath: string, projectContext: ProjectContext): boolean { - const normalizedTarget = this.normalizePath(targetPath); - const normalizedProjectRoot = this.normalizePath(projectContext.projectRoot); - const normalizedWorkspaceRoot = this.normalizePath(projectContext.workspaceRoot); - - // Path must be within either project root or workspace root - return normalizedTarget.startsWith(normalizedProjectRoot) || - normalizedTarget.startsWith(normalizedWorkspaceRoot); - } - - /** - * Normalizes path separators and resolves relative components - */ - private normalizePath(inputPath: string): string { - return path.resolve(inputPath).replace(/\\/g, '/'); - } - - /** - * Synchronously checks if a path exists (used for template discovery) - */ - private pathExistsSync(filePath: string): boolean { - try { - const fs = require('fs'); - return fs.existsSync(filePath); - } catch (error) { - return false; - } - } -} \ No newline at end of file +import * as path from 'node:path'; +import { CodebotError } from '../errors'; +import { ErrorType, type IPathResolver, type ProjectContext } from '../types'; + +export class PathResolver implements IPathResolver { + private static readonly FORBIDDEN_PATTERNS = [ + /\.\./, // Parent directory traversal + /^\/+/, // Absolute paths starting with / + /^[a-zA-Z]:\\/, // Windows absolute paths + /\0/, // Null bytes + /[<>:"|?*]/, // Invalid filename characters + ]; + + private static readonly MAX_PATH_LENGTH = 260; // Windows MAX_PATH limitation + private static readonly MAX_COMPONENT_NAME_LENGTH = 200; // Reasonable component name limit + private static readonly RESERVED_NAMES = [ + 'CON', + 'PRN', + 'AUX', + 'NUL', + 'COM1', + 'COM2', + 'COM3', + 'COM4', + 'COM5', + 'COM6', + 'COM7', + 'COM8', + 'COM9', + 'LPT1', + 'LPT2', + 'LPT3', + 'LPT4', + 'LPT5', + 'LPT6', + 'LPT7', + 'LPT8', + 'LPT9', + ]; + + /** + * Resolves the template path based on project context + * Follows hierarchy: project-specific templates → workspace templates → fallback + */ + resolveTemplatePath( + projectContext: ProjectContext, + templateName: string, + ): string { + // Check for dangerous security patterns first + if (!this.validatePathSecurity(templateName)) { + throw new CodebotError( + ErrorType.FILE_SYSTEM_ERROR, + `Invalid template name: ${templateName}`, + { templateName }, + false, + ); + } + + const sanitizedTemplateName = this.sanitizePath(templateName); + + // For multi-project workspaces, prefer project-specific templates + if (projectContext.isMultiProject) { + const projectTemplatePath = path.join( + projectContext.projectRoot, + 'templates', + sanitizedTemplateName, + ); + return this.normalizePath(projectTemplatePath); + } + + // For single project or fallback, use workspace-level templates + const workspaceTemplatePath = path.join( + projectContext.workspaceRoot, + 'templates', + sanitizedTemplateName, + ); + return this.normalizePath(workspaceTemplatePath); + } + + /** + * Resolves the target path where component will be created + * Ensures path is within project boundaries + */ + resolveTargetPath( + projectContext: ProjectContext, + componentName: string, + selectedFolderPath?: string, + ): string { + // Check for dangerous security patterns first + if (!this.validatePathSecurity(componentName)) { + throw new CodebotError( + ErrorType.INVALID_COMPONENT_NAME, + `Invalid component name: ${componentName}`, + { componentName }, + true, + ); + } + + const sanitizedComponentName = this.sanitizePath(componentName); + + // Determine the base path for component creation + const basePath = this.resolveBasePath(projectContext, selectedFolderPath); + const targetPath = path.join(basePath, sanitizedComponentName); + + // Ensure the resolved path is within project boundaries + if (!this.isWithinProjectBoundaries(targetPath, projectContext)) { + throw new CodebotError( + ErrorType.FILE_SYSTEM_ERROR, + `Target path is outside project boundaries: ${targetPath}`, + { targetPath, projectRoot: projectContext.projectRoot }, + false, + ); + } + + return this.normalizePath(targetPath); + } + + /** + * Validates a path for security and correctness (strict validation) + */ + validatePath(inputPath: string): boolean { + if (!inputPath || typeof inputPath !== 'string') { + return false; + } + + // Check path length + if (inputPath.length > PathResolver.MAX_PATH_LENGTH) { + return false; + } + + // Check for forbidden patterns + for (const pattern of PathResolver.FORBIDDEN_PATTERNS) { + if (pattern.test(inputPath)) { + return false; + } + } + + // Check for reserved names (Windows) + const pathParts = inputPath.split(/[/\\]/); + for (const part of pathParts) { + const upperPart = part.toUpperCase(); + if (PathResolver.RESERVED_NAMES.includes(upperPart)) { + return false; + } + } + + // Check for empty path segments + if (pathParts.some(part => part === '')) { + return false; + } + + return true; + } + + /** + * Validates a path strictly for dangerous security patterns only + */ + private validatePathSecurity(inputPath: string): boolean { + if (!inputPath || typeof inputPath !== 'string') { + return false; + } + + // Check component name length (reasonable limit) + if (inputPath.length > PathResolver.MAX_COMPONENT_NAME_LENGTH) { + return false; + } + + // Check for dangerous patterns only + const dangerousPatterns = [ + /\.\./, // Parent directory traversal + /^\/+/, // Absolute paths starting with / + /^[a-zA-Z]:\\/, // Windows absolute paths + /\0/, // Null bytes + ]; + + for (const pattern of dangerousPatterns) { + if (pattern.test(inputPath)) { + return false; + } + } + + return true; + } + + /** + * Sanitizes a path by removing dangerous characters and patterns + */ + sanitizePath(inputPath: string): string { + if (!inputPath || typeof inputPath !== 'string') { + return ''; + } + + let sanitized = inputPath + // Remove null bytes + .replace(/\0/g, '') + // Remove or replace invalid characters + .replace(/[<>:"|?*]/g, '_') + // Remove leading/trailing whitespace and dots + .trim() + .replace(/^\.+|\.+$/g, '') + // Normalize path separators + .replace(/[/\\]+/g, path.sep) + // Remove parent directory references + .replace(/\.\.[/\\]/g, '') + .replace(/\.\.$/, ''); + + // Ensure it doesn't start with a path separator + sanitized = sanitized.replace(/^[/\\]+/, ''); + + // Handle reserved names by appending underscore + const pathParts = sanitized.split(path.sep); + const sanitizedParts = pathParts.map(part => { + const upperPart = part.toUpperCase(); + if (PathResolver.RESERVED_NAMES.includes(upperPart)) { + return `${part}_`; + } + return part; + }); + + return sanitizedParts.join(path.sep); + } + + /** + * Resolves the configuration file path for a project + */ + resolveConfigPath(projectContext: ProjectContext): string { + // For multi-project, prefer project-specific config + if (projectContext.isMultiProject) { + const projectConfigPath = path.join( + projectContext.projectRoot, + 'codebot.config.json', + ); + return this.normalizePath(projectConfigPath); + } + + // For single project, use workspace-level config + const workspaceConfigPath = path.join( + projectContext.workspaceRoot, + 'codebot.config.json', + ); + return this.normalizePath(workspaceConfigPath); + } + + /** + * Resolves relative paths to absolute paths within project context + */ + resolveRelativePath( + projectContext: ProjectContext, + relativePath: string, + ): string { + // Check for dangerous security patterns first + if (!this.validatePathSecurity(relativePath)) { + throw new CodebotError( + ErrorType.FILE_SYSTEM_ERROR, + `Invalid relative path: ${relativePath}`, + { relativePath }, + false, + ); + } + + const sanitizedPath = this.sanitizePath(relativePath); + + const basePath = this.resolveBasePath(projectContext); + const absolutePath = path.resolve(basePath, sanitizedPath); + + // Ensure the resolved path is within project boundaries + if (!this.isWithinProjectBoundaries(absolutePath, projectContext)) { + throw new CodebotError( + ErrorType.FILE_SYSTEM_ERROR, + `Resolved path is outside project boundaries: ${absolutePath}`, + { relativePath, absolutePath, projectRoot: projectContext.projectRoot }, + false, + ); + } + + return this.normalizePath(absolutePath); + } + + /** + * Determines the base path for operations based on project context + */ + private resolveBasePath( + projectContext: ProjectContext, + selectedFolderPath?: string, + ): string { + // If a specific folder was selected, use that as the base path + if (selectedFolderPath) { + // Ensure the selected folder is within project boundaries + if (this.isWithinProjectBoundaries(selectedFolderPath, projectContext)) { + return selectedFolderPath; + } + } + + // Fallback to project root + return projectContext.projectRoot; + } + + /** + * Checks if a path is within the allowed project boundaries + */ + private isWithinProjectBoundaries( + targetPath: string, + projectContext: ProjectContext, + ): boolean { + const normalizedTarget = this.normalizePath(targetPath); + const normalizedProjectRoot = this.normalizePath( + projectContext.projectRoot, + ); + const normalizedWorkspaceRoot = this.normalizePath( + projectContext.workspaceRoot, + ); + + // Path must be within either project root or workspace root + return ( + normalizedTarget.startsWith(normalizedProjectRoot) || + normalizedTarget.startsWith(normalizedWorkspaceRoot) + ); + } + + /** + * Normalizes path separators and resolves relative components + */ + private normalizePath(inputPath: string): string { + return path.resolve(inputPath).replace(/\\/g, '/'); + } +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 26eb34b..69f1063 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,2 +1,2 @@ -export * from './validation'; -export * from './PathResolver'; \ No newline at end of file +export * from './PathResolver'; +export * from './validation'; diff --git a/src/utils/validation.ts b/src/utils/validation.ts index bb50349..e587067 100644 --- a/src/utils/validation.ts +++ b/src/utils/validation.ts @@ -1,37 +1,32 @@ -import { COMPONENT_NAME_REGEX } from '../constants'; - -export class ValidationUtils { - static isValidComponentName(name: string): boolean { - if (!name || typeof name !== 'string') { - return false; - } - - return COMPONENT_NAME_REGEX.test(name.trim()); - } - - static sanitizeComponentName(name: string): string { - return name.trim().replace(/[^a-zA-Z0-9_-]/g, ''); - } - - static isValidPath(path: string): boolean { - if (!path || typeof path !== 'string') { - return false; - } - - // Check for path traversal attempts - const normalizedPath = path.replace(/\\/g, '/'); - return !normalizedPath.includes('../') && !normalizedPath.includes('./'); - } - - static sanitizePath(path: string): string { - return path.replace(/[<>:"|?*]/g, '').replace(/\.\./g, ''); - } - - static isTemplateFile(filename: string): boolean { - return filename.endsWith('.hbs'); - } - - static removeTemplateExtension(filename: string): string { - return filename.replace(/\.hbs$/, ''); - } -} \ No newline at end of file +import { COMPONENT_NAME_REGEX } from '../constants'; + +export function isValidComponentName(name: string): boolean { + if (!name || typeof name !== 'string') { + return false; + } + return COMPONENT_NAME_REGEX.test(name.trim()); +} + +export function sanitizeComponentName(name: string): string { + return name.trim().replace(/[^a-zA-Z0-9_-]/g, ''); +} + +export function isValidPath(filePath: string): boolean { + if (!filePath || typeof filePath !== 'string') { + return false; + } + const normalizedPath = filePath.replace(/\\/g, '/'); + return !normalizedPath.includes('../') && !normalizedPath.includes('./'); +} + +export function sanitizePath(filePath: string): string { + return filePath.replace(/[<>:"|?*]/g, '').replace(/\.\./g, ''); +} + +export function isTemplateFile(filename: string): boolean { + return filename.endsWith('.hbs'); +} + +export function removeTemplateExtension(filename: string): string { + return filename.replace(/\.hbs$/, ''); +} diff --git a/tsconfig.json b/tsconfig.json index b65c745..d72ce84 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,21 +1,15 @@ { - "compilerOptions": { - "module": "commonjs", - "target": "es6", - "outDir": "out", - "lib": [ - "es6" - ], - "sourceMap": true, - "rootDir": "src", - "strict": true /* enable all strict type-checking options */ - /* Additional Checks */ - // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - // "noUnusedParameters": true, /* Report errors on unused parameters. */ - }, - "exclude": [ - "node_modules", - ".vscode-test" - ] + "compilerOptions": { + "module": "commonjs", + "target": "ES2022", + "outDir": "out", + "lib": ["ES2022"], + "sourceMap": true, + "rootDir": "src", + "strict": true, + "skipLibCheck": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "exclude": ["node_modules", ".vscode-test"] } diff --git a/webpack.config.js b/webpack.config.js index 5240fd0..9398550 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,4 +1,4 @@ -const path = require('path'); +const path = require('node:path'); const config = { target: 'node', @@ -7,7 +7,9 @@ const config = { output: { path: path.resolve(__dirname, 'dist'), filename: 'extension.js', - libraryTarget: 'commonjs2', + library: { + type: 'commonjs2', + }, }, devtool: 'nosources-source-map', externals: { From bcabb1ef5fc250ea1165b90ec9e6892b69c21819 Mon Sep 17 00:00:00 2001 From: Douglas Gomes Date: Fri, 17 Apr 2026 17:12:05 -0300 Subject: [PATCH 05/17] feat: add ComponentTailwind and ReactPackage examples - Introduced new ComponentTailwind files including styles, component logic, and index for exports. - Created ReactPackage structure with essential files such as package.json, Vite configuration, and main application entry point. - Added HTML template and setup for testing with Jest. - Removed outdated ComponentSass example to streamline the project. --- .../ComponentSass.stories.tsx.hbs | 11 --- .../ComponentTailwind.style.ts.hbs | 3 + .../ComponentTailwind.tsx.hbs | 20 +++++ examples/ComponentTailwind/index.tsx.hbs | 1 + examples/ReactPackage/index.html.hbs | 12 +++ examples/ReactPackage/package.json.hbs | 74 +++++++++++++++++++ examples/ReactPackage/postcss.config.cjs.hbs | 1 + examples/ReactPackage/src/App.tsx.hbs | 9 +++ examples/ReactPackage/src/lib/main.tsx.hbs | 1 + examples/ReactPackage/src/main.tsx.hbs | 12 +++ examples/ReactPackage/src/setupTests.ts.hbs | 1 + examples/ReactPackage/src/vite-env.d.ts.hbs | 2 + examples/ReactPackage/tsconfig.json.hbs | 32 ++++++++ examples/ReactPackage/tsconfig.node.json.hbs | 11 +++ examples/ReactPackage/vite.config.ts.hbs | 61 +++++++++++++++ .../StoriesTsx/StoriesTsx.stories.tsx.hbs | 15 ++++ 16 files changed, 255 insertions(+), 11 deletions(-) delete mode 100644 examples/ComponentSass/ComponentSass.stories.tsx.hbs create mode 100644 examples/ComponentTailwind/ComponentTailwind.style.ts.hbs create mode 100644 examples/ComponentTailwind/ComponentTailwind.tsx.hbs create mode 100644 examples/ComponentTailwind/index.tsx.hbs create mode 100644 examples/ReactPackage/index.html.hbs create mode 100644 examples/ReactPackage/package.json.hbs create mode 100644 examples/ReactPackage/postcss.config.cjs.hbs create mode 100644 examples/ReactPackage/src/App.tsx.hbs create mode 100644 examples/ReactPackage/src/lib/main.tsx.hbs create mode 100644 examples/ReactPackage/src/main.tsx.hbs create mode 100644 examples/ReactPackage/src/setupTests.ts.hbs create mode 100644 examples/ReactPackage/src/vite-env.d.ts.hbs create mode 100644 examples/ReactPackage/tsconfig.json.hbs create mode 100644 examples/ReactPackage/tsconfig.node.json.hbs create mode 100644 examples/ReactPackage/vite.config.ts.hbs create mode 100644 examples/StoriesTsx/StoriesTsx.stories.tsx.hbs diff --git a/examples/ComponentSass/ComponentSass.stories.tsx.hbs b/examples/ComponentSass/ComponentSass.stories.tsx.hbs deleted file mode 100644 index 5aefec6..0000000 --- a/examples/ComponentSass/ComponentSass.stories.tsx.hbs +++ /dev/null @@ -1,11 +0,0 @@ -import { ComponentStory, ComponentMeta } from '@storybook/react'; -import { {{name}} } from './{{name}}'; - -export default { - title: '{{name}}', - component: {{name}}, -} as ComponentMeta; - -const Template: ComponentStory = args => <{{name}} {...args} />; - -export const Primary = Template.bind({}); diff --git a/examples/ComponentTailwind/ComponentTailwind.style.ts.hbs b/examples/ComponentTailwind/ComponentTailwind.style.ts.hbs new file mode 100644 index 0000000..411564c --- /dev/null +++ b/examples/ComponentTailwind/ComponentTailwind.style.ts.hbs @@ -0,0 +1,3 @@ +import { createStyles } from 'ds'; + +export const {{name}}Style = createStyles({}) diff --git a/examples/ComponentTailwind/ComponentTailwind.tsx.hbs b/examples/ComponentTailwind/ComponentTailwind.tsx.hbs new file mode 100644 index 0000000..55a7ba5 --- /dev/null +++ b/examples/ComponentTailwind/ComponentTailwind.tsx.hbs @@ -0,0 +1,20 @@ +import { ReactNode } from 'react'; +import { VariantProps } from 'tailwind-variants'; + +import { {{name}}Style } from './{{name}}.style'; + +type {{name}}Variants = VariantProps; + +export type {{name}}Props = {{name}}Variants & { + children?: ReactNode; + className?: string; +} + +export function {{name}}({children,className}: {{name}}Props) { + return ( +
+

{{name}}

+ {children} +
+ ) +} diff --git a/examples/ComponentTailwind/index.tsx.hbs b/examples/ComponentTailwind/index.tsx.hbs new file mode 100644 index 0000000..4bcde88 --- /dev/null +++ b/examples/ComponentTailwind/index.tsx.hbs @@ -0,0 +1 @@ +export * from './{{name}}'; diff --git a/examples/ReactPackage/index.html.hbs b/examples/ReactPackage/index.html.hbs new file mode 100644 index 0000000..a4a2376 --- /dev/null +++ b/examples/ReactPackage/index.html.hbs @@ -0,0 +1,12 @@ + + + + + + SKY DS - {{name}} + + +
+ + + diff --git a/examples/ReactPackage/package.json.hbs b/examples/ReactPackage/package.json.hbs new file mode 100644 index 0000000..bdbd674 --- /dev/null +++ b/examples/ReactPackage/package.json.hbs @@ -0,0 +1,74 @@ +{ + "name": "@agentguru/{{name}}", + "version": "0.0.1", + "author": "AgentGuru", + "publishConfig": { + "registry": "https://npm.pkg.github.com/" + }, + "repository": { + "directory": "packages/{{name}}", + "type": "git", + "url": "https://github.com/agentguru/sky-ds.git" + }, + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "files": [ + "dist" + ], + "sideEffects": false, + "scripts": { + "build": "pnpm clean && tsc && vite build", + "clean": "pnpm rimraf dist", + "dev": "vite", + "preview": "vite preview", + "sync:packages": "ag-sync-packages-version -pkg {{name}}", + "test": "vitest run", + "test:coverage": "vitest run --coverage", + "test:ui": "vitest --ui", + "test:watch": "vitest" + }, + "dependencies": {}, + "devDependencies": { + "@agentguru/postcss-config": "^2.0.0", + "@agentguru/foundations": "^3.0.0", + "@agentguru/scripts": "^2.0.0", + "@storybook/react": "catalog:", + "@testing-library/jest-dom": "catalog:", + "@testing-library/react": "catalog:", + "@testing-library/react-hooks": "catalog:", + "@testing-library/user-event": "catalog:", + "@types/node": "catalog:", + "@types/react": "catalog:", + "@types/react-dom": "catalog:", + "@vitejs/plugin-react-swc": "catalog:", + "@vitest/ui": "catalog:", + "classnames": "catalog:", + "framer-motion": "catalog:", + "jsdom": "catalog:", + "postcss": "catalog:", + "react": "catalog:", + "react-dom": "catalog:", + "rimraf": "catalog:", + "sass": "catalog:", + "typescript": "catalog:", + "vite": "catalog:", + "vite-plugin-dts": "catalog:", + "vite-plugin-style-inject": "catalog:", + "vite-plugin-svgr": "catalog:", + "vitest": "catalog:" + }, + "peerDependencies": { + "@agentguru/foundations": "^3.0.0", + "classnames": "catalog:", + "framer-motion": "catalog:", + "react": "catalog:", + "react-dom": "catalog:" + } +} diff --git a/examples/ReactPackage/postcss.config.cjs.hbs b/examples/ReactPackage/postcss.config.cjs.hbs new file mode 100644 index 0000000..95968fb --- /dev/null +++ b/examples/ReactPackage/postcss.config.cjs.hbs @@ -0,0 +1 @@ +module.exports = require('@agentguru/postcss-config/react'); diff --git a/examples/ReactPackage/src/App.tsx.hbs b/examples/ReactPackage/src/App.tsx.hbs new file mode 100644 index 0000000..cd61342 --- /dev/null +++ b/examples/ReactPackage/src/App.tsx.hbs @@ -0,0 +1,9 @@ +function App() { + return ( +
+

SKY DS - {{name}}

+
+ ); +} + +export default App; diff --git a/examples/ReactPackage/src/lib/main.tsx.hbs b/examples/ReactPackage/src/lib/main.tsx.hbs new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/examples/ReactPackage/src/lib/main.tsx.hbs @@ -0,0 +1 @@ +export {}; diff --git a/examples/ReactPackage/src/main.tsx.hbs b/examples/ReactPackage/src/main.tsx.hbs new file mode 100644 index 0000000..4440155 --- /dev/null +++ b/examples/ReactPackage/src/main.tsx.hbs @@ -0,0 +1,12 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; + +import '@agentguru/foundations'; + +import App from './App'; + +ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( + + + , +); diff --git a/examples/ReactPackage/src/setupTests.ts.hbs b/examples/ReactPackage/src/setupTests.ts.hbs new file mode 100644 index 0000000..7b0828b --- /dev/null +++ b/examples/ReactPackage/src/setupTests.ts.hbs @@ -0,0 +1 @@ +import '@testing-library/jest-dom'; diff --git a/examples/ReactPackage/src/vite-env.d.ts.hbs b/examples/ReactPackage/src/vite-env.d.ts.hbs new file mode 100644 index 0000000..b1f45c7 --- /dev/null +++ b/examples/ReactPackage/src/vite-env.d.ts.hbs @@ -0,0 +1,2 @@ +/// +/// diff --git a/examples/ReactPackage/tsconfig.json.hbs b/examples/ReactPackage/tsconfig.json.hbs new file mode 100644 index 0000000..9929205 --- /dev/null +++ b/examples/ReactPackage/tsconfig.json.hbs @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "useUnknownInCatchVariables": false, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + + /* Paths */ + "baseUrl": "src", + "paths": { + "@/*": ["*"] + } + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/examples/ReactPackage/tsconfig.node.json.hbs b/examples/ReactPackage/tsconfig.node.json.hbs new file mode 100644 index 0000000..97ede7e --- /dev/null +++ b/examples/ReactPackage/tsconfig.node.json.hbs @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/examples/ReactPackage/vite.config.ts.hbs b/examples/ReactPackage/vite.config.ts.hbs new file mode 100644 index 0000000..1eb542d --- /dev/null +++ b/examples/ReactPackage/vite.config.ts.hbs @@ -0,0 +1,61 @@ +/// +import react from '@vitejs/plugin-react-swc'; +import path from 'node:path'; +import { defineConfig } from 'vite'; +import dts from 'vite-plugin-dts'; +import VitePluginStyleInject from 'vite-plugin-style-inject'; +import svgr from 'vite-plugin-svgr'; + +import pkgJSON from './package.json'; + +// https://vitejs.dev/config/ +export default defineConfig(({ mode }) => ({ + define: mode === 'test' ? {} : { global: 'window' }, + plugins: [ + react(), + dts({ + insertTypesEntry: true, + rollupTypes: true, + }), + VitePluginStyleInject(), + svgr(), + ], + build: { + sourcemap: false, + copyPublicDir: false, + minify: 'esbuild', + lib: { + entry: path.resolve(__dirname, 'src', 'lib', 'main.tsx'), + name: pkgJSON.name, + fileName: 'index', + formats: ['es'], + }, + rollupOptions: { + external: [...Object.keys(pkgJSON.peerDependencies)], + output: { + preserveModules: true, + inlineDynamicImports: false, + format: 'esm', + globals: { + react: 'React', + 'react-dom': 'ReactDOM', + }, + }, + }, + }, + css: { + modules: { generateScopedName: '[name]_[local]_[contenthash:base64:5]' }, + preprocessorOptions: { + scss: { + api: 'modern-compiler', + quietDeps: true, // Disable Sass deprecated warnings + silenceDeprecations: ['global-builtin', 'import'], // Disable deprecation warnings + }, + }, + }, + test: { + globals: true, + environment: 'jsdom', + setupFiles: ['./src/setupTests.ts'], + }, +})); diff --git a/examples/StoriesTsx/StoriesTsx.stories.tsx.hbs b/examples/StoriesTsx/StoriesTsx.stories.tsx.hbs new file mode 100644 index 0000000..aa7ab4c --- /dev/null +++ b/examples/StoriesTsx/StoriesTsx.stories.tsx.hbs @@ -0,0 +1,15 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { {{name}} } from './{{name}}'; + +const meta: Meta = { + title: '{{name}}', + component: {{name}}, + tags: ['autodocs'], +}; + +type Story = StoryObj; + +export default meta; + +export const Primary: Story = {} From 17bce0ad4aee02fb6af9febc387ea14c74fdc2b2 Mon Sep 17 00:00:00 2001 From: Douglas Gomes Date: Fri, 17 Apr 2026 17:28:53 -0300 Subject: [PATCH 06/17] feat: migrate to Biome for linting and add skill creation documentation - Replaced ESLint and Prettier with Biome for linting and formatting, updating related commands in package.json. - Introduced comprehensive documentation for creating skills, including structure, best practices, and progressive disclosure patterns. - Added multiple reference files to support skill creation, covering topics like writing guidelines, testing, and bundling for VSCode extensions. - Updated CLAUDE.md to reflect new linting commands and added a section on Biome configuration. --- .claude/skills/create-skill/SKILL.md | 150 +++++++++++ .../references/from-project-docs.md | 105 ++++++++ .../references/patterns-and-examples.md | 180 +++++++++++++ .../references/progressive-disclosure.md | 112 +++++++++ .../references/structure-and-metadata.md | 128 ++++++++++ .../references/writing-guidelines.md | 150 +++++++++++ .claude/skills/vscode-extension/SKILL.md | 169 +++++++++++++ .../references/bundling-and-publishing.md | 173 +++++++++++++ .../references/manifest-and-contributions.md | 166 ++++++++++++ .../vscode-extension/references/testing.md | 236 ++++++++++++++++++ .../references/tree-view-pattern.md | 127 ++++++++++ .../references/ux-editor-and-advanced.md | 200 +++++++++++++++ .../references/ux-notifications-inputs.md | 186 ++++++++++++++ .../references/workspace-and-files.md | 193 ++++++++++++++ CLAUDE.md | 11 +- package.json | 4 - src/helpers/getTextByInputBox.ts | 1 - 17 files changed, 2284 insertions(+), 7 deletions(-) create mode 100644 .claude/skills/create-skill/SKILL.md create mode 100644 .claude/skills/create-skill/references/from-project-docs.md create mode 100644 .claude/skills/create-skill/references/patterns-and-examples.md create mode 100644 .claude/skills/create-skill/references/progressive-disclosure.md create mode 100644 .claude/skills/create-skill/references/structure-and-metadata.md create mode 100644 .claude/skills/create-skill/references/writing-guidelines.md create mode 100644 .claude/skills/vscode-extension/SKILL.md create mode 100644 .claude/skills/vscode-extension/references/bundling-and-publishing.md create mode 100644 .claude/skills/vscode-extension/references/manifest-and-contributions.md create mode 100644 .claude/skills/vscode-extension/references/testing.md create mode 100644 .claude/skills/vscode-extension/references/tree-view-pattern.md create mode 100644 .claude/skills/vscode-extension/references/ux-editor-and-advanced.md create mode 100644 .claude/skills/vscode-extension/references/ux-notifications-inputs.md create mode 100644 .claude/skills/vscode-extension/references/workspace-and-files.md diff --git a/.claude/skills/create-skill/SKILL.md b/.claude/skills/create-skill/SKILL.md new file mode 100644 index 0000000..f995d50 --- /dev/null +++ b/.claude/skills/create-skill/SKILL.md @@ -0,0 +1,150 @@ +--- +name: create-skill +description: Create optimized Agent Skills for Cursor and other AI agents. Use when the user wants to create, write, structure, or improve a skill, asks about skill structure, SKILL.md format, best practices, progressive disclosure, or wants to generate skills from existing project documentation. +--- + +# Create Skill + +Guide for creating effective skills that extend agent capabilities with specialized knowledge, workflows, and tool integrations. + +## What Skills Provide + +1. **Specialized workflows** — Multi-step procedures for specific domains +2. **Tool integrations** — Instructions for working with file formats or APIs +3. **Domain expertise** — Company-specific knowledge, schemas, business logic +4. **Bundled resources** — Scripts, references, and assets for repetitive tasks + +## Skill Structure + +``` +skill-name/ +├── SKILL.md # Required, <200 lines +│ ├── YAML frontmatter # name + description (required) +│ └── Markdown body # Core instructions +└── Bundled Resources # Optional + ├── scripts/ # Executable code + ├── references/ # Documentation loaded on-demand + └── assets/ # Files used in output (templates, images) +``` + +### Storage Locations (Cursor) + +| Type | Path | Scope | +|------|------|-------| +| Personal | `~/.cursor/skills/skill-name/` | All your projects | +| Project | `.cursor/skills/skill-name/` | Shared via repository | + +> **Never** create skills in `~/.cursor/skills-cursor/` — reserved for Cursor built-in skills. + +See [references/structure-and-metadata.md](references/structure-and-metadata.md) for full details on frontmatter, naming, and bundled resources. + +## Progressive Disclosure (Critical) + +SKILL.md must be under **200 lines**. Split detailed content into `references/` files. + +### Three-Level Loading + +1. **Metadata** (name + description) — Always in context (~100 words) +2. **SKILL.md body** — Loaded when skill triggers (<200 lines) +3. **Bundled resources** — Loaded on-demand by agent (unlimited) + +This achieves ~85% reduction in context load. See [references/progressive-disclosure.md](references/progressive-disclosure.md) for patterns. + +## Core Principles + +1. **Always in English** — All skill files (SKILL.md, references, scripts, comments) must be written in English, regardless of the user's language. English maximizes agent comprehension and cross-team reusability. +2. **Concise is key** — The context window is shared. Only add what the agent doesn't already know. Challenge every paragraph: "Does this justify its token cost?" +3. **Degrees of freedom** — High (text) for flexible tasks, Medium (pseudocode) for preferred patterns, Low (scripts) for fragile operations +4. **Imperative writing** — Use verb-first: "Extract text with..." not "You should extract..." +5. **One default, not many options** — Provide a recommended approach with an escape hatch, not a menu of choices +6. **Test with multiple models** — Effectiveness varies by model + +See [references/writing-guidelines.md](references/writing-guidelines.md) for detailed guidance. + +## Creation Workflow + +### Step 1: Gather Requirements + +Understand the skill's purpose through concrete examples: + +- What specific task or workflow should this skill help with? +- When should the agent automatically apply it? (trigger scenarios) +- What domain knowledge does the agent need that it wouldn't already know? +- Should it be personal (`~/.cursor/skills/`) or project (`.cursor/skills/`)? + +Use the **AskQuestion** tool when available for structured gathering. If context from a previous conversation exists, infer the skill from what was discussed. + +### Step 2: Plan Resources + +Analyze each use case and identify reusable resources: + +- **Scripts** — Code that would be rewritten each time (e.g., `scripts/validate.py`) +- **References** — Documentation the agent should consult (e.g., `references/schema.md`) +- **Assets** — Files used in output, not loaded into context (e.g., `assets/template/`) + +### Step 3: Write the Skill + +1. Create the directory structure +2. Write SKILL.md with frontmatter: + - `name`: lowercase, hyphens only, max 64 chars + - `description`: specific, third-person, includes WHAT + WHEN triggers (max 1024 chars) +3. Write concise body (<200 lines) with core instructions +4. Create reference files for detailed content (<200 lines each) +5. Add scripts/assets as needed +6. Delete any unnecessary placeholder files + +### Step 4: Validate + +- [ ] **All files written in English** (even if user communicates in another language) +- [ ] SKILL.md under 200 lines +- [ ] Description is specific, third-person, includes WHAT and WHEN +- [ ] Consistent terminology throughout +- [ ] References are one level deep (no nested references) +- [ ] No time-sensitive information +- [ ] Examples are concrete, not abstract +- [ ] Scripts tested and working + +### Step 5: Iterate + +1. Use the skill on real tasks +2. Identify struggles or inefficiencies +3. Update SKILL.md or bundled resources +4. Test again with different models + +## Common Patterns + +See [references/patterns-and-examples.md](references/patterns-and-examples.md) for: + +- Template pattern (output format) +- Workflow pattern (checklists + sequential steps) +- Conditional workflow pattern (decision trees) +- Feedback loop pattern (validate → fix → repeat) +- Examples pattern (input/output pairs) +- Complete skill example + +## Creating Skills from Project Documentation + +For bootstrapping skills from existing project docs, see [references/from-project-docs.md](references/from-project-docs.md). + +## Anti-Patterns + +| Anti-Pattern | Fix | +|-------------|-----| +| Vague names (`helper`, `utils`) | Specific names (`processing-pdfs`, `code-review`) | +| Verbose explanations | Challenge every paragraph's token cost | +| Too many options | One default + escape hatch | +| Windows paths (`scripts\file.py`) | Forward slashes (`scripts/file.py`) | +| Time-sensitive info | "Current method" + "Old patterns (deprecated)" | +| Inconsistent terminology | Pick one term, use it everywhere | +| Monolithic 1000+ line SKILL.md | Split into references (<200 lines each) | +| Over-fragmented (50+ tiny files) | 5-10 focused reference files | + +## References + +| Topic | File | +|-------|------| +| Structure and metadata | [references/structure-and-metadata.md](references/structure-and-metadata.md) | +| Writing guidelines | [references/writing-guidelines.md](references/writing-guidelines.md) | +| Patterns and examples | [references/patterns-and-examples.md](references/patterns-and-examples.md) | +| Progressive disclosure | [references/progressive-disclosure.md](references/progressive-disclosure.md) | +| Skills from project docs | [references/from-project-docs.md](references/from-project-docs.md) | diff --git a/.claude/skills/create-skill/references/from-project-docs.md b/.claude/skills/create-skill/references/from-project-docs.md new file mode 100644 index 0000000..d350ece --- /dev/null +++ b/.claude/skills/create-skill/references/from-project-docs.md @@ -0,0 +1,105 @@ +# Creating Skills from Project Documentation + +Workflow for bootstrapping a skill from existing documentation in a project. + +## When to Use + +- User wants to create a skill from an existing project's documentation +- Source is local: current directory, `docs/`, `documentation/`, or repo root +- Goal is to make project knowledge accessible to agents + +## Workflow + +Copy this checklist and track progress: + +``` +Task Progress: +- [ ] Step 1: Identify documentation sources +- [ ] Step 2: Plan skill structure +- [ ] Step 3: Generate skill files +- [ ] Step 4: Review coverage and fill gaps +- [ ] Step 5: Validate and integrate +``` + +### Step 1: Identify Documentation Sources + +1. Look for common doc paths: `docs/`, `documentation/`, `packages/*/docs/`, README files at repo root +2. Analyze structure: sidebar, navigation, main topics, entry points +3. Categorize content into: `core`, `features`, `best-practices`, `advanced` +4. **Skip** content agents already know (general programming concepts, common library basics) +5. **Focus** on project-specific knowledge, custom APIs, business logic, schemas + +### Step 2: Plan Skill Structure + +Map documentation categories to reference files: + +``` +skills// +├── SKILL.md # Overview + reference table +└── references/ + ├── core-.md # Core concepts and fundamentals + ├── features-.md # Feature documentation + ├── best-practices-.md # Patterns and guidelines + └── advanced-.md # Advanced topics +``` + +Keep the naming consistent with kebab-case and category prefixes. + +### Step 3: Generate Skill Files + +1. **SKILL.md** (<200 lines): + - Frontmatter with `name` and `description` + - Brief intro (2-3 sentences) + - Sections with tables of references organized by category + - Quick start or most common workflow + +2. **Reference files** (one concept per file): + - Frontmatter-style heading with description + - Brief explanation of the concept + - **Usage** section with working code examples + - **Key Points** as concise bullets + - Source comment at the end: `` + +3. **Writing rules**: + - **Always write in English** — Even if source docs are in another language, translate and adapt all skill content to English + - **Rewrite for agents** — Don't copy docs verbatim. Be practical and concise. + - **One concept per file** — Keep references focused + - **Include working code examples** — Agents learn best from examples + - **Explain when and why, not just how** — Context helps agents make better decisions + +### Step 4: Review Coverage and Fill Gaps + +Loop until no major modules are missing: + +1. **Compare** generated references against the project's documented surface (docs tree, README, navigation) +2. **Identify gaps** — Focus on major modules only: + - Core concepts that are central to the project + - Main APIs or features commonly needed + - Primary workflows users would ask about +3. **Add** missing references with correct naming prefixes +4. **Update** SKILL.md reference table +5. **Stop when**: + - All core concepts are covered + - Main APIs/features have references + - Primary workflows are documented + - Only minor edge cases remain + +### Step 5: Validate and Integrate + +1. Run the standard validation checklist (see SKILL.md → Step 4: Validate) +2. Test by asking the agent questions the skill should help answer +3. Iterate based on agent performance + +### Optional: GENERATION.md + +For tracking purposes, create a `GENERATION.md` at the skill root: + +```markdown +# Generation Info + +- **Source:** (current project) +- **Generated:** +- **Coverage:** +``` + +This helps future maintainers understand the skill's origin and scope. diff --git a/.claude/skills/create-skill/references/patterns-and-examples.md b/.claude/skills/create-skill/references/patterns-and-examples.md new file mode 100644 index 0000000..a8d8d12 --- /dev/null +++ b/.claude/skills/create-skill/references/patterns-and-examples.md @@ -0,0 +1,180 @@ +# Common Patterns and Examples + +## Template Pattern + +Provide output format templates for consistent results: + +````markdown +## Report structure + +Use this template: + +```markdown +# [Analysis Title] + +## Executive summary +[One-paragraph overview of key findings] + +## Key findings +- Finding 1 with supporting data +- Finding 2 with supporting data + +## Recommendations +1. Specific actionable recommendation +2. Specific actionable recommendation +``` +```` + +For strict requirements, prefix with "ALWAYS use this exact template." +For flexible guidance, add "Adjust sections as needed." + +## Examples Pattern + +For output-quality-dependent skills, provide input/output pairs: + +````markdown +## Commit message format + +**Example 1:** +Input: Added user authentication with JWT tokens +Output: +``` +feat(auth): implement JWT-based authentication + +Add login endpoint and token validation middleware +``` + +**Example 2:** +Input: Fixed bug where dates displayed incorrectly +Output: +``` +fix(reports): correct date formatting in timezone conversion + +Use UTC timestamps consistently across report generation +``` +```` + +## Workflow Pattern + +Break complex operations into sequential steps with a checklist: + +````markdown +## Form filling workflow + +Copy this checklist and track progress: + +``` +Task Progress: +- [ ] Step 1: Analyze the form +- [ ] Step 2: Create field mapping +- [ ] Step 3: Validate mapping +- [ ] Step 4: Fill the form +- [ ] Step 5: Verify output +``` + +**Step 1: Analyze the form** +Run: `python scripts/analyze_form.py input.pdf` +... +```` + +## Conditional Workflow Pattern + +Guide through decision points: + +```markdown +## Document modification + +1. Determine modification type: + + **Creating new?** → Follow "Creation workflow" below + **Editing existing?** → Follow "Editing workflow" below + +2. Creation workflow: + - Use docx-js library + - Build document from scratch + ... + +3. Editing workflow: + - Modify XML directly + ... +``` + +## Feedback Loop Pattern + +For quality-critical tasks, implement validate → fix → repeat: + +```markdown +## Document editing process + +1. Make your edits +2. **Validate immediately**: `python scripts/validate.py output/` +3. If validation fails: + - Review the error message + - Fix the issues + - Run validation again +4. **Only proceed when validation passes** +``` + +This pattern greatly improves output quality for fragile operations. + +## Complete Skill Example + +**Directory structure:** + +``` +code-review/ +├── SKILL.md +└── references/ + ├── standards.md + └── examples.md +``` + +**SKILL.md:** + +```markdown +--- +name: code-review +description: Review code for quality, security, and maintainability following team standards. Use when reviewing pull requests, examining code changes, or when the user asks for a code review. +--- + +# Code Review + +## Quick Start + +When reviewing code: + +1. Check for correctness and potential bugs +2. Verify security best practices +3. Assess readability and maintainability +4. Ensure tests are adequate + +## Review Checklist + +- [ ] Logic is correct and handles edge cases +- [ ] No security vulnerabilities (SQL injection, XSS, etc.) +- [ ] Follows project style conventions +- [ ] Functions are focused and appropriately sized +- [ ] Error handling is comprehensive +- [ ] Tests cover the changes + +## Feedback Format + +- 🔴 **Critical**: Must fix before merge +- 🟡 **Suggestion**: Consider improving +- 🟢 **Nice to have**: Optional enhancement + +## References + +- Detailed coding standards: [references/standards.md](references/standards.md) +- Example reviews: [references/examples.md](references/examples.md) +``` + +## Balanced Skill Sizing + +| Type | Lines | Problem | +|------|-------|---------| +| **Monolithic** | 1000+ | Slow activation, hard to navigate | +| **Over-fragmented** | 50+ files | Hard to understand, excessive file reads | +| **Balanced** | ~150 SKILL.md + 5-10 refs | Fast activation, clear structure | + +Aim for the balanced approach: a concise SKILL.md that serves as a navigation hub, with 5-10 focused reference files for detailed content. diff --git a/.claude/skills/create-skill/references/progressive-disclosure.md b/.claude/skills/create-skill/references/progressive-disclosure.md new file mode 100644 index 0000000..b7f91ff --- /dev/null +++ b/.claude/skills/create-skill/references/progressive-disclosure.md @@ -0,0 +1,112 @@ +# Progressive Disclosure + +## Core Rule + +SKILL.md must be under **200 lines**. Every skill exceeding this should be refactored. While skills up to 500 lines can still perform, splitting into references is always preferred for optimal performance. + +## Three-Level Loading + +1. **Metadata** (~100 words) — Always in agent context. Determines when skill triggers. +2. **SKILL.md body** (<200 lines) — Loaded when skill activates. Core instructions only. +3. **References** (unlimited) — Loaded on-demand when agent needs specific details. + +### Why It Matters + +- ~85% reduction in initial context load +- Faster skill activation (under 100ms vs 500ms+) +- Agent loads only what's needed, when it's needed +- Skills remain maintainable and focused + +## Disclosure Patterns + +### Pattern 1: High-level guide with references + +```markdown +# PDF Processing + +## Quick start +[Essential instructions — enough to handle 80% of cases] + +## Advanced features +- **Form filling**: See [references/forms.md](references/forms.md) +- **API reference**: See [references/api.md](references/api.md) +- **Examples**: See [references/examples.md](references/examples.md) +``` + +### Pattern 2: Domain-specific organization + +Organize by domain when a skill covers multiple areas: + +``` +bigquery-skill/ +├── SKILL.md (overview + navigation) +└── references/ + ├── finance.md (revenue, billing) + ├── sales.md (opportunities, pipeline) + ├── product.md (API usage, features) + └── marketing.md (campaigns, attribution) +``` + +Agent reads only the relevant domain file (e.g., sales question → only `sales.md`). + +### Pattern 3: Framework/variant organization + +``` +cloud-deploy/ +├── SKILL.md (workflow + provider selection) +└── references/ + ├── aws.md + ├── gcp.md + └── azure.md +``` + +### Pattern 4: Conditional details + +```markdown +# DOCX Processing + +## Creating documents +Use docx-js. See [references/creation.md](references/creation.md). + +## Editing documents +For simple edits, modify XML directly. + +**For tracked changes**: See [references/redlining.md](references/redlining.md) +**For OOXML details**: See [references/ooxml.md](references/ooxml.md) +``` + +## Rules + +1. **One level deep** — All references link directly from SKILL.md. Never chain references (reference → reference → reference). +2. **Reference files <200 lines** — Split further if needed. +3. **Table of contents** — For reference files >100 lines, add a ToC at the top so the agent can see the full scope on preview. +4. **No duplication** — Information lives in SKILL.md OR references, not both. +5. **Clear navigation** — SKILL.md must tell the agent what each reference contains and when to read it. + +## Refactoring a Monolithic Skill + +**Before** (1000-line SKILL.md): + +- Everything in one file +- Slow to load, hard to maintain + +**After** (well-structured): + +``` +my-skill/ +├── SKILL.md # 150 lines — core principles, overview, reference table +└── references/ + ├── components.md # 150 lines + ├── routing.md # 100 lines + ├── forms.md # 120 lines + └── styling.md # 80 lines +``` + +### Steps to Refactor + +1. Identify the core instructions (keep in SKILL.md) +2. Group related detailed content into reference files +3. Replace detailed sections in SKILL.md with one-line references +4. Add a reference table at the bottom of SKILL.md +5. Verify no duplication between SKILL.md and references +6. Test that the agent can still complete tasks effectively diff --git a/.claude/skills/create-skill/references/structure-and-metadata.md b/.claude/skills/create-skill/references/structure-and-metadata.md new file mode 100644 index 0000000..3e00d34 --- /dev/null +++ b/.claude/skills/create-skill/references/structure-and-metadata.md @@ -0,0 +1,128 @@ +# Skill Structure and Metadata + +## Directory Layout + +``` +skill-name/ +├── SKILL.md # Required, <200 lines +├── references/ # Optional - documentation loaded on-demand +│ ├── core-concept.md +│ ├── features-api.md +│ └── advanced-patterns.md +├── scripts/ # Optional - executable code +│ ├── validate.py +│ └── helper.sh +└── assets/ # Optional - files used in output (not loaded into context) + ├── template/ + └── logo.png +``` + +## YAML Frontmatter + +Every SKILL.md requires frontmatter with exactly two fields: + +```yaml +--- +name: skill-name +description: What the skill does and when to use it. +--- +``` + +Do not include any other fields in the frontmatter. + +### `name` Field + +- Max 64 characters +- Lowercase letters, numbers, and hyphens only +- Prefer gerund form: `processing-pdfs`, `analyzing-data`, `managing-databases` +- Acceptable alternatives: noun phrases (`pdf-processing`) or action-oriented (`process-pdfs`) +- **Avoid** vague names: `helper`, `utils`, `tools`, `documents`, `data` + +### `description` Field + +The description is the **primary trigger mechanism**. The agent reads it to decide when to use the skill. + +- Max 1024 characters +- Write in **third person** (it's injected into the system prompt) +- Include **WHAT** (specific capabilities) and **WHEN** (trigger scenarios) +- Be specific and include key terms users would naturally mention +- Include all "when to use" info here — the body loads only AFTER triggering, so "When to Use" sections in the body are wasted + +**Good examples:** + +```yaml +# PDF Processing +description: Extract text and tables from PDF files, fill forms, merge documents. Use when working with PDF files or when the user mentions PDFs, forms, or document extraction. + +# Code Review +description: Review code for quality, security, and maintainability following team standards. Use when reviewing pull requests, examining code changes, or when the user asks for a code review. + +# Git Commit Helper +description: Generate descriptive commit messages by analyzing git diffs. Use when the user asks for help writing commit messages or reviewing staged changes. + +# Excel Analysis +description: Analyze Excel spreadsheets, create pivot tables, generate charts. Use when analyzing Excel files, spreadsheets, tabular data, or .xlsx files. +``` + +**Bad examples:** + +```yaml +description: Helps with documents # Too vague, no triggers +description: I can help you process files # Not third person +description: Use this to process data # Not specific enough +``` + +## Bundled Resources + +### Scripts (`scripts/`) + +Executable code for deterministic, repetitive tasks: + +- More reliable than agent-generated code each time +- Save tokens (no code generation needed) +- Ensure consistency across uses +- Include error handling and clear documentation +- List required packages in instructions or `requirements.txt` +- Prefer Python or Node.js over Bash (better cross-platform support) +- Always test scripts before including them + +Clarify in SKILL.md whether the agent should **execute** or **read** each script. + +### References (`references/`) + +Documentation loaded on-demand into agent context: + +- Keep each file <200 lines when possible +- For files >100 lines, include a table of contents at the top +- One concept per file +- Use category prefixes for organization: `core-*.md`, `features-*.md`, `best-practices-*.md`, `advanced-*.md` +- **No duplication** — information lives in SKILL.md OR references, not both +- Sacrifice grammar for concision when needed + +### Assets (`assets/`) + +Files used in agent output, never loaded into context: + +- Templates, images, icons, fonts, boilerplate code +- Separates output resources from documentation +- Agent can copy/modify these files without reading them into context + +## Storage Locations + +### Cursor + +| Type | Path | Scope | +|------|------|-------| +| Personal | `~/.cursor/skills/skill-name/` | Available across all projects | +| Project | `.cursor/skills/skill-name/` | Shared via repository | + +> **Never** create skills in `~/.cursor/skills-cursor/` — reserved for Cursor built-in skills. + +### Other Agents + +| Agent | Personal | Project | +|-------|----------|---------| +| Claude Code | `~/.claude/skills/` | `.claude/skills/` | +| Generic | `~/.agent/skills/` | `.agent/skills/` | + +Adjust paths based on your agent harness. diff --git a/.claude/skills/create-skill/references/writing-guidelines.md b/.claude/skills/create-skill/references/writing-guidelines.md new file mode 100644 index 0000000..94411f2 --- /dev/null +++ b/.claude/skills/create-skill/references/writing-guidelines.md @@ -0,0 +1,150 @@ +# Writing Guidelines + +## Language: Always English + +All skill files must be written in **English**, regardless of the language the user communicates in. This includes: + +- SKILL.md (frontmatter and body) +- All reference files +- Scripts and code comments +- Asset descriptions + +English maximizes agent comprehension across all models and ensures cross-team reusability. If the source documentation is in another language, translate and adapt the content to English when generating skill files. + +## Conciseness + +The context window is a shared resource. Your skill competes with conversation history, other skills, and the actual request. + +**Default assumption**: Agents are already very smart. Only add context they don't already have. + +Challenge each piece of information: + +- "Does the agent really need this explanation?" +- "Can I assume the agent already knows this?" +- "Does this paragraph justify its token cost?" + +**Good** (~50 tokens): + +```markdown +## Extract PDF text + +Use pdfplumber: + +​```python +import pdfplumber + +with pdfplumber.open("file.pdf") as pdf: + text = pdf.pages[0].extract_text() +​``` +``` + +**Bad** (~150 tokens): + +```markdown +## Extract PDF text + +PDF (Portable Document Format) files are a common file format that contains +text, images, and other content. To extract text from a PDF, you'll need to +use a library. There are many libraries available for PDF processing, but we +recommend pdfplumber because it's easy to use... +``` + +## Writing Style + +- Use **imperative/infinitive form** (verb-first): "Extract text with..." not "You should extract..." +- Use objective, instructional language +- Sacrifice grammar for concision when needed in references +- Prefer concise code examples over verbose explanations + +## Description Best Practices + +### Rules + +1. **Third person** — "Processes files" not "I process files" +2. **Specific** — Include exact capabilities and key terms +3. **WHAT + WHEN** — What it does AND when to trigger it +4. **Key terms** — Include words users would naturally mention + +### Template + +```yaml +description: [Capability 1], [Capability 2], [Capability 3]. Use when [trigger 1], [trigger 2], or [trigger 3]. +``` + +## Terminology + +Choose one term and use it consistently throughout the entire skill: + +| Pick one | Not these | +|----------|-----------| +| API endpoint | URL, route, path | +| field | box, element, control | +| extract | pull, get, retrieve | +| validate | check, verify, test | + +## Degrees of Freedom + +Match specificity to the task's fragility: + +| Level | When | Example | +|-------|------|---------| +| **High** (text instructions) | Multiple valid approaches | Code review guidelines | +| **Medium** (pseudocode/templates) | Preferred pattern exists | Report generation | +| **Low** (specific scripts) | Fragile/critical operations | Database migrations | + +Think of it as: narrow bridge with cliffs needs guardrails (low freedom), open field allows many routes (high freedom). + +## One Default, Not Many Options + +**Bad** — Confusing: + +```markdown +You can use pypdf, or pdfplumber, or PyMuPDF, or pdf2image... +``` + +**Good** — Clear default with escape hatch: + +```markdown +Use pdfplumber for text extraction. +For scanned PDFs requiring OCR, use pdf2image with pytesseract instead. +``` + +## Time-Sensitive Information + +Avoid it. If unavoidable, use this pattern: + +```markdown +## Current method + +Use the v2 API endpoint. + +## Old patterns (deprecated) + +
+Legacy v1 API +... +
+``` + +## Evaluation-Driven Development + +Build evaluations before writing extensive documentation: + +1. **Identify gaps** — Run agent on tasks without the skill. Document failures. +2. **Create evaluations** — Build 3+ scenarios that test these gaps. +3. **Establish baseline** — Measure agent performance without the skill. +4. **Write minimal instructions** — Only enough to address gaps and pass evaluations. +5. **Iterate** — Execute evaluations, compare against baseline, refine. + +This ensures the skill solves real problems rather than documenting imagined ones. + +## Iterative Development + +Work with one agent instance ("Agent A") to create the skill, test with another ("Agent B"): + +1. Complete a task without a skill — notice what info you repeatedly provide +2. Identify the reusable pattern +3. Ask Agent A to create the skill +4. Review for conciseness (remove unnecessary explanations) +5. Test with Agent B on real tasks +6. Iterate based on observation diff --git a/.claude/skills/vscode-extension/SKILL.md b/.claude/skills/vscode-extension/SKILL.md new file mode 100644 index 0000000..99dea0e --- /dev/null +++ b/.claude/skills/vscode-extension/SKILL.md @@ -0,0 +1,169 @@ +--- +name: vscode-extension +description: Best practices and patterns for developing VSCode extensions. Covers extension anatomy, activation, command registration, contribution points, UX guidelines, storage APIs, bundling with webpack, and publishing. Trigger when working on VSCode extension code (extension.ts, package.json contributes, vscode API usage, commands, tree views, webviews) or when asked about VSCode extension development. +--- + +# VSCode Extension Development Best Practices + +## Extension Anatomy + +Every extension has two entry points: + +```typescript +// src/extension.ts +import * as vscode from 'vscode'; + +export function activate(context: vscode.ExtensionContext) { + // Register all disposables into context.subscriptions + // They are automatically disposed when the extension deactivates +} + +export function deactivate() { + // Optional: explicit async cleanup +} +``` + +**Rule:** Always push disposables to `context.subscriptions`. Never leak listeners, watchers, or panels. + +## Activation Events + +Declare in `package.json` → `activationEvents`. Load lazily — prefer specific events over `*`. + +| Event | When to use | +|-------|-------------| +| `onCommand:id` | Extension provides commands (VSCode 1.74+ auto-registers) | +| `onLanguage:id` | Language-specific features | +| `workspaceContains:glob` | Activates when matching file found in workspace | +| `onStartupFinished` | Needs to run after startup but not immediately | +| `*` | Never — activates on every startup, slows VSCode | + +Since **VSCode 1.74+**, commands, views, and languages declared in `contributes` are auto-activated — `activationEvents` entries for those are optional. + +## Command Registration + +```typescript +// Good: push to subscriptions, use category prefix +context.subscriptions.push( + vscode.commands.registerCommand('myExt.doSomething', async () => { + // handler + }) +); + +// Good: when/group for menu placement +// In package.json contributes.menus: +// "explorer/context": [{ "command": "myExt.doSomething", "when": "explorerResourceIsFolder", "group": "1_modification" }] +``` + +Command IDs must be namespaced: `.`. + +## Contribution Points Quick Reference + +See [references/manifest-and-contributions.md](references/manifest-and-contributions.md) for full field list. + +Key points in `package.json → contributes`: +- `commands` — title, icon, category, enablement condition +- `menus` — placement (`explorer/context`, `editor/title`, `view/title`, etc.) +- `configuration` — user settings with JSON schema +- `views` / `viewsContainers` — sidebar panels +- `keybindings` — keyboard shortcuts with `when` clauses + +## Storage APIs + +```typescript +// Workspace-scoped (per workspace) +context.workspaceState.get('key', defaultValue); +context.workspaceState.update('key', value); + +// Global (across all workspaces) +context.globalState.get('key', defaultValue); +context.globalState.update('key', value); + +// Secrets (encrypted, for tokens/passwords) +await context.secrets.store('token', value); +const token = await context.secrets.get('token'); + +// Large files (use URIs, not state) +const storageUri = context.globalStorageUri; // persist across sessions +``` + +## UX Patterns + +- Use **Quick Picks** (`vscode.window.showQuickPick`) for multi-option selection +- Use **Input Box** (`vscode.window.showInputBox`) for single text input with validation +- Use **Output Channel** for logging (`vscode.window.createOutputChannel`) +- Use **Progress** for long operations (`vscode.window.withProgress`) +- Use **Status Bar Items** for persistent status — set `alignment` and `priority` +- **Avoid notifications for non-critical info** — prefer Output Channel or Status Bar + +UX details → [references/ux-notifications-inputs.md](references/ux-notifications-inputs.md) | [references/ux-editor-and-advanced.md](references/ux-editor-and-advanced.md) + +## Webviews + +```typescript +const panel = vscode.window.createWebviewPanel( + 'myViewType', 'My Panel', vscode.ViewColumn.One, + { enableScripts: true, retainContextWhenHidden: true } +); +context.subscriptions.push(panel); + +// Bidirectional messaging +panel.webview.postMessage({ type: 'update', data }); +panel.webview.onDidReceiveMessage(msg => { /* handle */ }, undefined, context.subscriptions); +``` + +Always set **CSP with nonce** and use **VSCode CSS variables** for theme support. +Full webview guide → [references/ux-editor-and-advanced.md](references/ux-editor-and-advanced.md) + +## Tree Views + +Implement `vscode.TreeDataProvider`. Register with `vscode.window.createTreeView`. +Full pattern → [references/tree-view-pattern.md](references/tree-view-pattern.md) + +## Workspace & File System + +```typescript +// Always use vscode.workspace.fs for workspace files (supports remote: SSH/WSL) +const bytes = await vscode.workspace.fs.readFile(uri); +const entries = await vscode.workspace.fs.readDirectory(dirUri); + +// Safe URI construction — never string concat +const child = vscode.Uri.joinPath(rootFolder.uri, 'src', 'index.ts'); + +// Watch for changes +const watcher = vscode.workspace.createFileSystemWatcher('**/*.hbs'); +watcher.onDidChange(() => invalidateCache()); +context.subscriptions.push(watcher); +``` + +Full API reference → [references/workspace-and-files.md](references/workspace-and-files.md) + +## Testing + +- Use **Jest + vscode mock** for unit/integration tests — no extension host needed +- Mock `vscode` at `src/test/__mocks__/vscode.ts` via `moduleNameMapper` +- Test commands by asserting on `vscode.window.showInformationMessage` / `showErrorMessage` calls +- Use `@vscode/test-cli` only for true E2E tests that need actual VSCode behavior + +Full testing guide → [references/testing.md](references/testing.md) + +## Bundling & Publishing + +- Bundle with **webpack** or **esbuild** — never ship raw TypeScript +- Mark `vscode` as external in bundler config +- Use `.vscodeignore` to exclude `src/`, `node_modules/`, test files +- Run `vsce package` → inspect before publishing + +Full checklist → [references/bundling-and-publishing.md](references/bundling-and-publishing.md) + +## Common Pitfalls + +| Pitfall | Fix | +|---------|-----| +| Not disposing resources | Push everything to `context.subscriptions` | +| Blocking `activate()` | Make it async, defer heavy work | +| String-concat paths | Use `vscode.Uri.joinPath` / `context.asAbsolutePath` | +| `import 'fs'` without protocol | Use `import * as fs from 'node:fs'` | +| Webview without CSP | Always set CSP with nonce in webview HTML | +| `*` activation event | Use specific events; 1.74+ auto-activates contributes | +| `node:fs` for workspace files | Use `vscode.workspace.fs` (supports remote workspaces) | +| Secrets in workspaceState | Use `context.secrets` for tokens/passwords | diff --git a/.claude/skills/vscode-extension/references/bundling-and-publishing.md b/.claude/skills/vscode-extension/references/bundling-and-publishing.md new file mode 100644 index 0000000..fdc5e92 --- /dev/null +++ b/.claude/skills/vscode-extension/references/bundling-and-publishing.md @@ -0,0 +1,173 @@ +# Bundling & Publishing + +## Webpack Configuration (Current Best Practice) + +```javascript +// webpack.config.js +const path = require('node:path'); + +module.exports = { + target: 'node', + mode: 'none', + entry: './src/extension.ts', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'extension.js', + library: { type: 'commonjs2' }, // webpack 5 syntax (not libraryTarget) + }, + externals: { + vscode: 'commonjs vscode', // never bundle vscode — it's provided by the host + }, + resolve: { + extensions: ['.ts', '.js'], + }, + module: { + rules: [{ test: /\.ts$/, exclude: /node_modules/, use: 'ts-loader' }], + }, + devtool: 'nosources-source-map', // dev builds; use hidden-source-map for production +}; +``` + +## package.json Scripts + +```json +"scripts": { + "vscode:prepublish": "npm run package", + "compile": "webpack", + "watch": "webpack --watch", + "package": "webpack --mode production --devtool hidden-source-map", + "build": "vsce package", + "lint": "biome check --write .", + "lint:check": "biome ci .", + "test": "jest", + "dev-install": "npm run compile && vsce package && code --install-extension myext-$(node -p \"require('./package.json').version\").vsix --force" +} +``` + +## tsconfig.json + +```json +{ + "compilerOptions": { + "module": "commonjs", + "target": "ES2022", + "lib": ["ES2022"], + "outDir": "out", + "rootDir": "src", + "strict": true, + "skipLibCheck": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "sourceMap": true + }, + "exclude": ["node_modules", ".vscode-test"] +} +``` + +## .vscodeignore + +``` +src/** +**/*.ts +**/*.map +tsconfig.json +webpack.config.js +biome.json +jest.config.js +.vscode-test/** +node_modules/** +coverage/** +out/** +.github/** +CONTRIBUTING.md +``` + +## Publishing Checklist + +Before publishing: +- [ ] `npm run lint:check` passes clean +- [ ] `npm test` — all tests pass +- [ ] `npm run build` — `.vsix` created without errors +- [ ] Inspect `.vsix` contents: `npx @vscode/vsce ls` +- [ ] README describes features with screenshots/GIFs +- [ ] CHANGELOG has entry for new version +- [ ] Icon is 128×128px PNG (not SVG) +- [ ] `engines.vscode` matches minimum tested version + +## Publishing Commands + +```bash +# One-time setup +npm install -g @vscode/vsce +vsce login # requires Azure DevOps PAT + +# Package only +vsce package # creates .vsix + +# Publish +vsce publish # uses current version +vsce publish patch # auto-bumps patch (1.0.0 → 1.0.1) +vsce publish minor # auto-bumps minor (1.0.0 → 1.1.0) +vsce publish --pre-release # publish to pre-release channel +``` + +## GitHub Actions CI/CD + +```yaml +# .github/workflows/release.yml +name: Release +on: + push: + branches: [main] + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '24' + - run: npm ci + - run: npm run lint:check + - run: npm test + - run: npm run package + - name: Publish + run: npx @vscode/vsce publish + env: + VSCE_PAT: ${{ secrets.VSCE_PAT }} +``` + +## Semantic Release Integration + +```json +// package.json — release config +"release": { + "branches": ["main"], + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + "@semantic-release/changelog", + ["@semantic-release/git", { "assets": ["CHANGELOG.md", "package.json"] }] + ] +} +``` + +Commit convention triggers: +- `fix:` → patch bump +- `feat:` → minor bump +- `feat!:` or `BREAKING CHANGE:` → major bump + +## Testing with @vscode/test-cli (Modern Approach) + +```bash +npm install --save-dev @vscode/test-cli @vscode/test-electron +``` + +```typescript +// .vscode-test.mjs +import { defineConfig } from '@vscode/test-cli'; +export default defineConfig({ files: 'out/test/**/*.test.js' }); +``` + +Or keep **Jest** with `ts-jest` (no extension host needed) for unit/integration tests that mock the `vscode` module — simpler and faster for most extension logic. diff --git a/.claude/skills/vscode-extension/references/manifest-and-contributions.md b/.claude/skills/vscode-extension/references/manifest-and-contributions.md new file mode 100644 index 0000000..cfba612 --- /dev/null +++ b/.claude/skills/vscode-extension/references/manifest-and-contributions.md @@ -0,0 +1,166 @@ +# Extension Manifest & Contribution Points + +## package.json Required Fields + +```json +{ + "name": "my-extension", // lowercase, no spaces, unique in Marketplace + "displayName": "My Extension", // shown in Marketplace UI + "description": "...", + "version": "1.0.0", // SemVer + "publisher": "your-publisher-id", + "license": "MIT", + "engines": { + "vscode": "^1.100.0" // minimum VSCode version supported + }, + "categories": ["Other"], // Programming Languages | Linters | Themes | Snippets | Other + "icon": "assets/icon.png", // 128x128px PNG + "main": "./dist/extension.js", // bundled entry point + "activationEvents": [], // omit entries for commands/views (1.74+ auto-activates) + "contributes": { ... } +} +``` + +## Full Contribution Points Reference + +### commands +```json +"commands": [ + { + "command": "myExt.action", + "title": "Do Something", + "category": "My Extension", // groups in Command Palette: "My Extension: Do Something" + "icon": "$(symbol-event)", // codicon or { "light": "path", "dark": "path" } + "enablement": "editorHasSelection" // when clause + } +] +``` + +### menus +Common menu locations: +- `commandPalette` — show/hide from palette via `when` +- `explorer/context` — right-click on file/folder +- `editor/context` — right-click in editor +- `editor/title` — top-right of editor tab +- `editor/title/context` — right-click on editor tab +- `view/title` — top of a tree view panel +- `view/item/context` — right-click on tree item +- `scm/title` — source control panel header + +```json +"menus": { + "explorer/context": [ + { + "command": "myExt.action", + "when": "explorerResourceIsFolder", + "group": "1_modification" // controls position; "navigation" = top, "inline" = icon + } + ] +} +``` + +### configuration +```json +"configuration": { + "title": "My Extension", + "properties": { + "myExt.templatePath": { + "type": "string", + "default": "templates", + "description": "Path to templates folder relative to workspace root", + "scope": "resource" // resource | window | application | machine + }, + "myExt.enabled": { + "type": "boolean", + "default": true, + "markdownDescription": "Enable **My Extension** features" + } + } +} +``` + +### views & viewsContainers +```json +"viewsContainers": { + "activitybar": [ + { + "id": "myExtContainer", + "title": "My Extension", + "icon": "assets/icon.svg" + } + ] +}, +"views": { + "myExtContainer": [ + { "id": "myExt.mainView", "name": "Main View" } + ], + "explorer": [ + { "id": "myExt.explorerView", "name": "My Items" } // adds to Explorer panel + ] +} +``` + +### keybindings +```json +"keybindings": [ + { + "command": "myExt.action", + "key": "ctrl+shift+p", + "mac": "cmd+shift+p", + "when": "editorTextFocus" + } +] +``` + +### snippets +```json +"snippets": [ + { "language": "typescript", "path": "./snippets/typescript.json" } +] +``` + +### languages +```json +"languages": [ + { + "id": "myLang", + "aliases": ["My Language"], + "extensions": [".myl"], + "configuration": "./language-configuration.json" + } +] +``` + +## .vscodeignore + +Exclude files from the packaged `.vsix`: +``` +src/** +**/*.ts +**/*.map +tsconfig.json +webpack.config.js +biome.json +jest.config.js +.vscode-test/** +node_modules/** +coverage/** +out/** +``` + +## Marketplace Requirements + +- Icon must be PNG (not SVG) +- Badge/image URLs must use HTTPS +- README displayed on Marketplace page +- CHANGELOG recommended +- Max 30 keywords in `package.json` + +## Version Bumping + +```bash +vsce publish patch # 1.0.0 → 1.0.1 +vsce publish minor # 1.0.0 → 1.1.0 +vsce publish major # 1.0.0 → 2.0.0 +vsce publish --pre-release # pre-release channel +``` diff --git a/.claude/skills/vscode-extension/references/testing.md b/.claude/skills/vscode-extension/references/testing.md new file mode 100644 index 0000000..6336804 --- /dev/null +++ b/.claude/skills/vscode-extension/references/testing.md @@ -0,0 +1,236 @@ +# Testing VSCode Extensions + +## Two Testing Strategies + +| Strategy | Tool | Use when | +|----------|------|----------| +| **Unit/integration** | Jest + vscode mock | Testing business logic, commands, managers — no extension host needed, fast | +| **E2E (extension host)** | `@vscode/test-cli` | Testing actual VSCode integration (UI, real file system, activation) | + +This project uses **Jest + vscode mock** — the preferred approach for testing extension logic without spinning up VSCode. + +## Jest Setup + +```javascript +// jest.config.js +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + roots: ['/src'], + testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'], + transform: { '^.+\\.ts$': 'ts-jest' }, + moduleNameMapper: { + '^vscode$': '/src/test/__mocks__/vscode.ts' + }, + setupFilesAfterEnach: ['/src/test/setup.ts'], + collectCoverageFrom: ['src/**/*.ts', '!src/**/*.d.ts', '!src/test/**/*'], + maxWorkers: 1, + forceExit: true, +}; +``` + +## vscode Mock Structure + +```typescript +// src/test/__mocks__/vscode.ts +export const window = { + showInformationMessage: jest.fn(), + showWarningMessage: jest.fn(), + showErrorMessage: jest.fn(), + showInputBox: jest.fn(), + showQuickPick: jest.fn(), + createOutputChannel: jest.fn(() => ({ + appendLine: jest.fn(), + show: jest.fn(), + dispose: jest.fn(), + })), + createStatusBarItem: jest.fn(() => ({ + show: jest.fn(), hide: jest.fn(), dispose: jest.fn(), text: '', tooltip: '' + })), + activeTextEditor: undefined, +}; + +export const workspace = { + getConfiguration: jest.fn(() => ({ + get: jest.fn(), + update: jest.fn(), + has: jest.fn(), + })), + workspaceFolders: undefined, + onDidChangeWorkspaceFolders: jest.fn(() => ({ dispose: jest.fn() })), + onDidChangeConfiguration: jest.fn(() => ({ dispose: jest.fn() })), + findFiles: jest.fn(), + fs: { + readFile: jest.fn(), + writeFile: jest.fn(), + stat: jest.fn(), + readDirectory: jest.fn(), + createDirectory: jest.fn(), + delete: jest.fn(), + }, +}; + +export const commands = { + registerCommand: jest.fn(() => ({ dispose: jest.fn() })), + executeCommand: jest.fn(), +}; + +export const Uri = { + file: jest.fn((path: string) => ({ fsPath: path, path, scheme: 'file' })), + parse: jest.fn(), + joinPath: jest.fn((base: { fsPath: string }, ...parts: string[]) => ({ + fsPath: [base.fsPath, ...parts].join('/'), + })), +}; + +export const ExtensionContext = jest.fn(() => ({ + subscriptions: [], + workspaceState: { get: jest.fn(), update: jest.fn() }, + globalState: { get: jest.fn(), update: jest.fn(), setKeysForSync: jest.fn() }, + secrets: { get: jest.fn(), store: jest.fn(), delete: jest.fn() }, + extensionUri: Uri.file('/mock/extension'), + extensionPath: '/mock/extension', + globalStorageUri: Uri.file('/mock/storage'), + storagePath: '/mock/storage', + asAbsolutePath: jest.fn((rel: string) => `/mock/extension/${rel}`), +})); + +export enum TreeItemCollapsibleState { None = 0, Collapsed = 1, Expanded = 2 } +export enum DiagnosticSeverity { Error = 0, Warning = 1, Information = 2, Hint = 3 } +export class TreeItem { constructor(public label: string, public collapsibleState?: TreeItemCollapsibleState) {} } +export class Range { constructor(public start: any, public end: any) {} } +export class Position { constructor(public line: number, public character: number) {} } +export class ThemeIcon { constructor(public id: string) {} } +export class ThemeColor { constructor(public id: string) {} } +export class EventEmitter { + event = jest.fn(); + fire = jest.fn(); + dispose = jest.fn(); +} +``` + +## Writing Unit Tests + +### Testing a Command Handler + +```typescript +// src/test/commands/createComponent.test.ts +import { createComponent } from '../../commands/createComponent'; +import * as vscode from 'vscode'; + +describe('createComponent', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('shows error when no folder path provided', async () => { + await createComponent({}); + + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith( + expect.stringContaining('No folder path') + ); + }); + + it('shows info on successful creation', async () => { + (vscode.window.showInputBox as jest.Mock).mockResolvedValue('MyComponent'); + (vscode.window.showQuickPick as jest.Mock).mockResolvedValue({ label: 'Component' }); + + await createComponent({ fsPath: '/workspace/src' }); + + expect(vscode.window.showInformationMessage).toHaveBeenCalledWith( + expect.stringContaining('MyComponent') + ); + }); + + it('returns early when user cancels input box', async () => { + (vscode.window.showInputBox as jest.Mock).mockResolvedValue(undefined); + + await createComponent({ fsPath: '/workspace/src' }); + + expect(vscode.window.showInformationMessage).not.toHaveBeenCalled(); + }); +}); +``` + +### Testing a Manager Class + +```typescript +// src/test/managers/TemplateManager.test.ts +import * as fs from 'node:fs'; +import { TemplateManager } from '../../managers/TemplateManager'; + +jest.mock('node:fs'); +const mockFs = fs as jest.Mocked; + +describe('TemplateManager', () => { + let manager: TemplateManager; + + beforeEach(() => { + manager = new TemplateManager(); + jest.clearAllMocks(); + }); + + it('returns empty array when templates folder does not exist', async () => { + mockFs.existsSync.mockReturnValue(false); + + const result = await manager.discoverTemplates('/workspace'); + + expect(result).toEqual([]); + }); + + it('discovers templates from folder', async () => { + mockFs.existsSync.mockReturnValue(true); + mockFs.readdirSync.mockReturnValue(['ComponentA', 'ComponentB'] as any); + + const result = await manager.discoverTemplates('/workspace'); + + expect(result).toHaveLength(2); + expect(result[0].name).toBe('ComponentA'); + }); +}); +``` + +## Mocking ExtensionContext + +```typescript +function makeContext(): vscode.ExtensionContext { + return { + subscriptions: [], + workspaceState: { get: jest.fn(), update: jest.fn() }, + globalState: { get: jest.fn(), update: jest.fn(), setKeysForSync: jest.fn() }, + secrets: { get: jest.fn(), store: jest.fn(), delete: jest.fn() }, + extensionUri: vscode.Uri.file('/ext'), + asAbsolutePath: (rel: string) => `/ext/${rel}`, + } as unknown as vscode.ExtensionContext; +} +``` + +## Coverage + +```bash +npm run test:coverage # generates coverage/ folder +# Open coverage/index.html to view by file +``` + +Target: >80% coverage on `src/managers/` and `src/commands/`. Lower bar for `src/helpers/` (thin wrappers). + +## E2E with @vscode/test-cli (when needed) + +```bash +npm install --save-dev @vscode/test-cli @vscode/test-electron +``` + +```typescript +// .vscode-test.mjs +import { defineConfig } from '@vscode/test-cli'; +export default defineConfig({ + files: 'out/test/**/*.test.js', + workspaceFolder: './test-fixtures', +}); +``` + +```bash +npx vscode-test # runs inside actual VSCode extension host +``` + +Use only when testing VSCode-specific behavior (activation, real FileSystem, UI interactions) — Jest is faster for logic. diff --git a/.claude/skills/vscode-extension/references/tree-view-pattern.md b/.claude/skills/vscode-extension/references/tree-view-pattern.md new file mode 100644 index 0000000..8686ae9 --- /dev/null +++ b/.claude/skills/vscode-extension/references/tree-view-pattern.md @@ -0,0 +1,127 @@ +# Tree View Pattern + +## Full TreeDataProvider Implementation + +```typescript +import * as vscode from 'vscode'; + +interface MyItem { + label: string; + children?: MyItem[]; + contextValue?: string; // used in "when" clauses for menus +} + +class MyTreeDataProvider implements vscode.TreeDataProvider { + private _onDidChangeTreeData = new vscode.EventEmitter(); + readonly onDidChangeTreeData = this._onDidChangeTreeData.event; + + refresh(): void { + this._onDidChangeTreeData.fire(); + } + + getTreeItem(element: MyItem): vscode.TreeItem { + const item = new vscode.TreeItem( + element.label, + element.children?.length + ? vscode.TreeItemCollapsibleState.Collapsed + : vscode.TreeItemCollapsibleState.None + ); + item.contextValue = element.contextValue; // enables "when: viewItem == myType" + item.iconPath = new vscode.ThemeIcon('file'); // or Uri for custom icon + item.tooltip = `Tooltip for ${element.label}`; + item.command = { // click action + command: 'myExt.openItem', + title: 'Open', + arguments: [element] + }; + return item; + } + + getChildren(element?: MyItem): MyItem[] { + if (!element) return this.getRootItems(); + return element.children ?? []; + } + + private getRootItems(): MyItem[] { + return [ + { label: 'Section A', children: [{ label: 'Item 1', contextValue: 'leaf' }] }, + { label: 'Section B', contextValue: 'leaf' } + ]; + } +} + +// Registration in activate() +export function activate(context: vscode.ExtensionContext) { + const provider = new MyTreeDataProvider(); + + const treeView = vscode.window.createTreeView('myExt.treeViewId', { + treeDataProvider: provider, + showCollapseAll: true, // adds collapse-all button to view title + canSelectMany: false + }); + + // Refresh on relevant events + context.subscriptions.push( + treeView, + vscode.commands.registerCommand('myExt.refresh', () => provider.refresh()), + vscode.workspace.onDidChangeWorkspaceFolders(() => provider.refresh()) + ); +} +``` + +## package.json for Tree View + +```json +"contributes": { + "viewsContainers": { + "activitybar": [ + { "id": "myExtContainer", "title": "My Extension", "icon": "assets/icon.svg" } + ] + }, + "views": { + "myExtContainer": [ + { "id": "myExt.treeViewId", "name": "My Items" } + ] + }, + "commands": [ + { "command": "myExt.refresh", "title": "Refresh", "icon": "$(refresh)" }, + { "command": "myExt.deleteItem", "title": "Delete Item", "icon": "$(trash)" } + ], + "menus": { + "view/title": [ + { "command": "myExt.refresh", "when": "view == myExt.treeViewId", "group": "navigation" } + ], + "view/item/context": [ + { + "command": "myExt.deleteItem", + "when": "view == myExt.treeViewId && viewItem == leaf", + "group": "inline" + } + ] + } +} +``` + +## Revealing Items Programmatically + +```typescript +// Requires createTreeView (not registerTreeDataProvider) +await treeView.reveal(item, { select: true, focus: true, expand: true }); +``` + +## Welcome View (empty state) + +```json +"viewsWelcome": [ + { + "view": "myExt.treeViewId", + "contents": "No templates found.\n[Create Template](command:myExt.createTemplate)\nOr [learn more](https://example.com).", + "when": "myExt.isEmpty" + } +] +``` + +Set the context key via: +```typescript +vscode.commands.executeCommand('setContext', 'myExt.isEmpty', true); +``` diff --git a/.claude/skills/vscode-extension/references/ux-editor-and-advanced.md b/.claude/skills/vscode-extension/references/ux-editor-and-advanced.md new file mode 100644 index 0000000..f8349b9 --- /dev/null +++ b/.claude/skills/vscode-extension/references/ux-editor-and-advanced.md @@ -0,0 +1,200 @@ +# UX: Editor, Webviews & Advanced Patterns + +## Webview — Theme-Aware Styling + +Always use VSCode CSS variables so webviews respect the active theme: + +```html + +``` + +## Webview — Content Security Policy (CSP) + +Always set CSP to prevent XSS. Use a per-load nonce for inline scripts: + +```typescript +function getWebviewContent(webview: vscode.Webview, nonce: string): string { + const cspSource = webview.cspSource; + return ` + + + + + + + + + +`; +} + +function getNonce(): string { + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + return Array.from({ length: 32 }, () => chars[Math.floor(Math.random() * chars.length)]).join(''); +} +``` + +## Webview — Bidirectional Messaging + +```typescript +// Extension → Webview +panel.webview.postMessage({ type: 'update', payload: data }); + +// Webview → Extension (in extension host) +panel.webview.onDidReceiveMessage( + message => { + switch (message.type) { + case 'save': handleSave(message.payload); break; + case 'cancel': panel.dispose(); break; + } + }, + undefined, + context.subscriptions +); + +// In webview JS (acquireVsCodeApi() call must be exactly once per page) +const vscode = acquireVsCodeApi(); +vscode.postMessage({ type: 'save', payload: formData }); +window.addEventListener('message', event => { + const { type, payload } = event.data; + if (type === 'update') renderData(payload); +}); +``` + +## Editor Decorations + +Highlight ranges in the editor (inline warnings, annotations, gutter icons): + +```typescript +const decorationType = vscode.window.createTextEditorDecorationType({ + backgroundColor: new vscode.ThemeColor('editor.findMatchHighlightBackground'), + borderRadius: '2px', + after: { + contentText: ' ← template variable', + color: new vscode.ThemeColor('editorCodeLens.foreground'), + fontStyle: 'italic', + margin: '0 0 0 8px', + }, + gutterIconPath: context.asAbsolutePath('assets/gutter-icon.svg'), + gutterIconSize: 'contain', +}); +context.subscriptions.push(decorationType); + +// Apply — call again to update, call with [] to clear +function applyDecorations(editor: vscode.TextEditor) { + const ranges: vscode.DecorationOptions[] = [ + { range: new vscode.Range(0, 0, 0, 10), hoverMessage: 'This is a variable' } + ]; + editor.setDecorations(decorationType, ranges); +} + +// Update on editor change +context.subscriptions.push( + vscode.window.onDidChangeActiveTextEditor(editor => { + if (editor) applyDecorations(editor); + }) +); +``` + +## Diagnostics (Problems Panel) + +```typescript +const diagnostics = vscode.languages.createDiagnosticCollection('myExt'); +context.subscriptions.push(diagnostics); + +function updateDiagnostics(uri: vscode.Uri, issues: Array<{ range: vscode.Range; message: string }>) { + diagnostics.set(uri, issues.map(({ range, message }) => + new vscode.Diagnostic(range, message, vscode.DiagnosticSeverity.Warning) + )); +} + +diagnostics.clear(); // clear all files +diagnostics.delete(uri); // clear one file +``` + +## When Clauses (Context Keys) + +Control command/menu visibility: + +``` +explorerResourceIsFolder — selected item is a folder +explorerResourceIsRoot — selected is workspace root +editorTextFocus — editor has keyboard focus +editorHasSelection — text is selected +editorLangId == typescript — active editor language +resourceExtname == .ts — file extension match +resourceFilename == package.json — exact filename match +view == myExt.treeViewId — specific view is active +viewItem == dependency — tree item contextValue match +isWorkspaceTrusted — workspace trust granted +config.myExt.enabled — setting is truthy +``` + +Set custom context keys from extension code: + +```typescript +vscode.commands.executeCommand('setContext', 'myExt.hasTemplates', true); +// Use in package.json when clause: "myExt.hasTemplates" +``` + +## Menu Groups & Separators + +```json +"menus": { + "explorer/context": [ + { "command": "myExt.create", "group": "1_modification@1" }, + { "command": "myExt.update", "group": "1_modification@2" }, + { "command": "myExt.delete", "group": "9_cutcopypaste@1" } + ] +} +``` + +- Groups sort alphabetically — `1_`, `2_`, `9_` control section order +- `@N` suffix orders items within the same group +- Built-in groups: `navigation` (always top), `1_modification`, `6_copypastecut`, `9_cutcopypaste` +- `"group": "inline"` renders as an icon button on tree item hover + +## UX Do's and Don'ts + +| Do | Don't | +|----|-------| +| Use `category` in commands to group in Command Palette | Flood palette with uncategorized commands | +| Confirm before destructive actions | Delete files/state silently | +| Show progress for operations > 2s | Leave users with no feedback | +| Use `when` clauses to hide irrelevant commands | Show all commands always | +| Use VSCode CSS variables in webviews | Hardcode colors — breaks themes | +| Persist settings via `contributes.configuration` | Store config in arbitrary files | +| Log details to Output Channel | Only `console.log` (invisible to users) | +| Use `context.asAbsolutePath()` for asset paths | Hardcode absolute paths | +| Use `vscode.Uri.joinPath()` for URI construction | String concatenation for paths | diff --git a/.claude/skills/vscode-extension/references/ux-notifications-inputs.md b/.claude/skills/vscode-extension/references/ux-notifications-inputs.md new file mode 100644 index 0000000..79ca96e --- /dev/null +++ b/.claude/skills/vscode-extension/references/ux-notifications-inputs.md @@ -0,0 +1,186 @@ +# UX: Notifications, Inputs & Basic UI + +## Core Principle + +Extensions must feel native to VSCode. Use established VSCode UI components instead of custom solutions whenever possible. + +## VSCode UI Architecture + +``` +Activity Bar → Sidebar (Primary/Secondary) → Editor Area + ↓ + Panel (Terminal, Output, Problems) +Status Bar (always visible across all areas) +``` + +## Notification Hierarchy + +Use the right level — over-notification trains users to ignore messages. + +```typescript +// Informational — temporary, non-blocking +vscode.window.showInformationMessage('Component created!'); + +// Warning — requires attention +vscode.window.showWarningMessage('Template folder is empty.'); + +// Error — action failed +vscode.window.showErrorMessage('Failed to read config file.'); + +// With actions (buttons) +const choice = await vscode.window.showInformationMessage( + 'Config not found. Create one?', + 'Yes', 'No' +); +if (choice === 'Yes') { /* ... */ } +``` + +**Rule:** Use Output Channel for logs and diagnostics, not `showInformationMessage`. + +## Quick Pick + +```typescript +// Simple list +const selected = await vscode.window.showQuickPick( + ['Option A', 'Option B', 'Option C'], + { placeHolder: 'Select an option', title: 'My Extension' } +); + +// Rich items with descriptions +const items: vscode.QuickPickItem[] = [ + { label: '$(file) Component', description: 'React component', detail: 'Creates index.tsx + styles' }, + { label: '$(folder) Page', description: 'Next.js page' } +]; +const picked = await vscode.window.showQuickPick(items, { placeHolder: 'Select template' }); + +// Multiple selection +const multi = await vscode.window.showQuickPick(items, { + canPickMany: true, + placeHolder: 'Select files to include' +}); +``` + +## Advanced QuickPick (dynamic filtering + buttons) + +```typescript +const qp = vscode.window.createQuickPick(); +qp.title = 'Select Template'; +qp.placeholder = 'Type to filter...'; +qp.matchOnDescription = true; +qp.buttons = [vscode.QuickInputButtons.Back]; + +qp.onDidChangeValue(value => { + qp.items = getFilteredItems(value); // dynamic filtering +}); +qp.onDidAccept(() => { + const selected = qp.selectedItems[0]; + qp.hide(); + handleSelection(selected); +}); +qp.onDidTriggerButton(btn => { + if (btn === vscode.QuickInputButtons.Back) qp.hide(); +}); + +qp.show(); +context.subscriptions.push(qp); // always dispose +``` + +## Input Box + +```typescript +const name = await vscode.window.showInputBox({ + title: 'Component Name', + placeHolder: 'e.g. UserProfile', + prompt: 'Enter the component name in PascalCase', + validateInput: value => { + if (!value || value.trim().length === 0) return 'Name cannot be empty'; + if (!/^[A-Z][a-zA-Z0-9]*$/.test(value)) return 'Must be PascalCase (e.g. MyComponent)'; + return null; // valid + } +}); +if (!name) return; // user cancelled +``` + +## Progress Reporting + +```typescript +// Notification-area spinner +await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: 'Generating component...', + cancellable: false + }, + async (progress) => { + progress.report({ increment: 0, message: 'Scanning templates...' }); + // ... work ... + progress.report({ increment: 50, message: 'Writing files...' }); + progress.report({ increment: 100 }); + } +); + +// Window-level (spinner in status bar area) +vscode.window.withProgress({ location: vscode.ProgressLocation.Window, title: 'Loading...' }, task); +``` + +## Output Channel + +```typescript +// Create once, reuse across the extension lifetime +const output = vscode.window.createOutputChannel('My Extension'); +context.subscriptions.push(output); + +output.appendLine('[INFO] Extension activated'); +output.appendLine(`[ERROR] ${error.message}`); +output.show(); // reveal the Output panel +``` + +## Status Bar Item + +```typescript +const statusBar = vscode.window.createStatusBarItem( + vscode.StatusBarAlignment.Right, + 100 // priority: higher = more to the left within alignment +); +statusBar.text = '$(check) Ready'; +statusBar.tooltip = 'My Extension is active'; +statusBar.command = 'myExt.showDetails'; +statusBar.show(); +context.subscriptions.push(statusBar); +``` + +## File & Folder Dialogs + +```typescript +// Open file picker +const uris = await vscode.window.showOpenDialog({ + canSelectFiles: true, + canSelectFolders: false, + canSelectMany: false, + filters: { 'Templates': ['hbs', 'mustache'] } +}); + +// Save file picker +const saveUri = await vscode.window.showSaveDialog({ + defaultUri: vscode.Uri.file('/default/path/file.json'), + filters: { 'JSON': ['json'] } +}); +``` + +## Codicons + +Use VSCode's built-in icon font for command icons and UI: + +``` +$(add) plus sign $(refresh) refresh arrows +$(trash) delete $(file) file +$(folder) folder $(gear) settings +$(check) checkmark $(warning) warning triangle +$(error) error circle $(info) info circle +$(search) magnifier $(edit) pencil +$(close) X $(chevron-right) arrow right +$(copy) copy $(terminal) terminal +$(run) play button $(stop) stop square +``` + +Full list: https://code.visualstudio.com/api/references/icons-in-labels diff --git a/.claude/skills/vscode-extension/references/workspace-and-files.md b/.claude/skills/vscode-extension/references/workspace-and-files.md new file mode 100644 index 0000000..cb6c240 --- /dev/null +++ b/.claude/skills/vscode-extension/references/workspace-and-files.md @@ -0,0 +1,193 @@ +# Workspace & File System API + +## Workspace Folders + +```typescript +// Get all open folders +const folders = vscode.workspace.workspaceFolders; +if (!folders || folders.length === 0) { + throw new Error('No workspace folder open'); +} + +const rootFolder = folders[0]; +const rootPath = rootFolder.uri.fsPath; + +// Multi-root workspace +folders.forEach(folder => { + console.log(folder.name, folder.uri.fsPath); +}); + +// Listen for folder changes +context.subscriptions.push( + vscode.workspace.onDidChangeWorkspaceFolders(e => { + e.added.forEach(f => console.log('Added:', f.uri.fsPath)); + e.removed.forEach(f => console.log('Removed:', f.uri.fsPath)); + }) +); +``` + +## Uri Construction (always use these — never string concat) + +```typescript +// From absolute path +const uri = vscode.Uri.file('/absolute/path/to/file.ts'); + +// Join segments safely +const child = vscode.Uri.joinPath(rootFolder.uri, 'src', 'components', 'index.ts'); + +// Parse from string (use for non-file URIs) +const untitled = vscode.Uri.parse('untitled:NewFile.ts'); + +// Get fsPath (cross-platform absolute path string) +console.log(child.fsPath); // /workspace/src/components/index.ts +``` + +## vscode.workspace.fs (Async, Platform-Safe) + +Prefer `vscode.workspace.fs` over `node:fs` when working with workspace resources — it handles remote workspaces (SSH, WSL, containers) correctly. + +```typescript +// Read file +const bytes = await vscode.workspace.fs.readFile(uri); +const content = Buffer.from(bytes).toString('utf8'); + +// Write file +const encoder = new TextEncoder(); +await vscode.workspace.fs.writeFile(uri, encoder.encode('file content')); + +// Check existence +try { + await vscode.workspace.fs.stat(uri); + // file exists +} catch { + // file does not exist (throws FileNotFound) +} + +// Create directory (recursive) +await vscode.workspace.fs.createDirectory(uri); + +// List directory +const entries = await vscode.workspace.fs.readDirectory(dirUri); +// entries: Array<[name: string, type: FileType]> +entries.forEach(([name, type]) => { + if (type === vscode.FileType.Directory) { /* folder */ } + if (type === vscode.FileType.File) { /* file */ } +}); + +// Delete (recursive for folders) +await vscode.workspace.fs.delete(uri, { recursive: true, useTrash: true }); + +// Copy / rename +await vscode.workspace.fs.copy(sourceUri, targetUri, { overwrite: false }); +await vscode.workspace.fs.rename(oldUri, newUri, { overwrite: false }); +``` + +## Find Files in Workspace + +```typescript +// Glob search across workspace +const files = await vscode.workspace.findFiles( + '**/*.hbs', // include pattern + '**/node_modules/**' // exclude pattern +); +// files: vscode.Uri[] + +// With token to cancel long searches +const token = new vscode.CancellationTokenSource(); +const results = await vscode.workspace.findFiles('**/*.ts', undefined, 100, token.token); +``` + +## File System Watcher + +```typescript +const watcher = vscode.workspace.createFileSystemWatcher( + new vscode.RelativePattern(rootFolder, 'templates/**/*.hbs') +); + +watcher.onDidCreate(uri => { /* new template added */ }); +watcher.onDidChange(uri => { /* template modified — invalidate cache */ }); +watcher.onDidDelete(uri => { /* template removed */ }); + +context.subscriptions.push(watcher); +``` + +## Opening & Showing Files + +```typescript +// Open in editor +const doc = await vscode.workspace.openTextDocument(uri); +await vscode.window.showTextDocument(doc); + +// Open with specific options +await vscode.window.showTextDocument(doc, { + viewColumn: vscode.ViewColumn.Beside, // open to the right + preview: false, // pin the tab + selection: new vscode.Range(0, 0, 0, 0) +}); + +// Open external URL / file +await vscode.env.openExternal(vscode.Uri.parse('https://example.com')); +``` + +## Text Document Events + +```typescript +context.subscriptions.push( + vscode.workspace.onDidOpenTextDocument(doc => { + if (doc.languageId === 'handlebars') { /* ... */ } + }), + vscode.workspace.onDidSaveTextDocument(doc => { + if (doc.fileName.endsWith('.hbs')) invalidateCache(); + }), + vscode.workspace.onDidChangeTextDocument(e => { + const changes = e.contentChanges; + // e.document is the changed document + }) +); +``` + +## Configuration + +```typescript +// Read (resource-scoped — respects folder overrides in multi-root) +const config = vscode.workspace.getConfiguration('myExt', folderUri); +const templatePath = config.get('templatePath', 'templates'); + +// Write (target: Global, Workspace, or WorkspaceFolder) +await config.update('templatePath', 'custom/templates', vscode.ConfigurationTarget.Workspace); + +// Listen for changes +context.subscriptions.push( + vscode.workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('myExt')) { + reloadConfig(); + } + }) +); +``` + +## node:fs vs vscode.workspace.fs + +| | `node:fs` | `vscode.workspace.fs` | +|---|---|---| +| Remote workspaces (SSH/WSL) | No | Yes | +| Untitled/virtual URIs | No | Yes | +| Speed (local) | Faster | Slightly slower | +| Use when | Bundler scripts, CLI tools, tests | Extension runtime code | + +For extension runtime code that touches workspace files, always prefer `vscode.workspace.fs`. + +## Path Utilities + +```typescript +import * as path from 'node:path'; + +// Build paths for local (non-workspace) use +const templatesDir = path.join(context.extensionPath, 'templates'); + +// For workspace resources, use Uri.joinPath instead: +const templatesUri = vscode.Uri.joinPath(context.extensionUri, 'templates'); + +// Get extension's absolute path to a bundled asset +const iconPath = context.asAbsolutePath('assets/icon.png'); +``` diff --git a/CLAUDE.md b/CLAUDE.md index 8661c5c..27529f7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,8 +9,8 @@ npm run compile # Webpack build (development) npm run watch # Webpack watch mode npm run package # Production build with hidden source maps npm run build # Package extension as .vsix -npm run lint # ESLint + Prettier with auto-fix -npm run lint:check # Lint check without fixing (used in CI) +npm run lint # Biome check with auto-fix +npm run lint:check # Biome check without fixing (used in CI) npm test # Run Jest tests npm run test:watch # Jest watch mode npm run test:coverage # Generate coverage reports @@ -64,6 +64,13 @@ Filenames containing the template folder name (or the literal string "Template") Webpack bundles everything into a single `dist/extension.js`. The `vscode` module is marked as external (provided by the host). TypeScript targets ES6 with strict mode enabled. +## Linting & formatting + +[Biome](https://biomejs.dev/) replaces ESLint and Prettier. Config lives in `biome.json` at the project root. Rules follow Biome's recommended set; formatting matches the previous Prettier config (single quotes, 2-space indent, trailing commas, LF line endings). + +- `npm run lint:check` — check only, exits non-zero on warnings (used in CI) +- `npm run lint` — check and auto-fix + ## Testing Jest with `ts-jest`. The `vscode` module is mocked globally (see `src/test/__mocks__/vscode.ts`). Tests live alongside source in `src/test/`. diff --git a/package.json b/package.json index 0e173d8..9d557cd 100644 --- a/package.json +++ b/package.json @@ -15,10 +15,6 @@ "Snippets", "Other" ], - "activationEvents": [ - "onCommand:codebot.createComponent", - "onCommand:codebot.updateComponent" - ], "main": "./dist/extension.js", "contributes": { "commands": [ diff --git a/src/helpers/getTextByInputBox.ts b/src/helpers/getTextByInputBox.ts index 265d6f5..4c0dc07 100644 --- a/src/helpers/getTextByInputBox.ts +++ b/src/helpers/getTextByInputBox.ts @@ -4,6 +4,5 @@ export async function getTextByInputBox(label: string) { return vscode.window.showInputBox({ prompt: label, ignoreFocusOut: true, - valueSelection: [-1, -1], }); } From a493ab67a02a967de2d5d1eeed3914d4beeba58d Mon Sep 17 00:00:00 2001 From: Douglas Gomes Date: Fri, 17 Apr 2026 23:09:46 -0300 Subject: [PATCH 07/17] feat: remove CLAUDE.md and update package.json for versioning and configuration - Deleted CLAUDE.md as it is no longer needed. - Updated package.json to bump version from 1.0.0 to 2.0.0 and added configuration for templatesFolderPath. - Refactored extension.ts to import commands from their respective files for better organization. - Introduced new mock files for testing and added comprehensive tests for createComponent and updateComponent commands. --- CLAUDE.md | 80 -- package.json | 12 +- src/__mocks__/vscode.js | 49 ++ .../commands/createComponent.test.ts | 428 ++++++++++ .../commands/updateComponent.test.ts | 349 ++++++++ .../helpers/config/readConfig.test.ts | 75 ++ src/__tests__/helpers/fs/createFile.test.ts | 73 ++ src/__tests__/helpers/fs/createFolder.test.ts | 45 + src/__tests__/helpers/fs/fileExists.test.ts | 32 + src/__tests__/helpers/fs/folderExists.test.ts | 30 + src/__tests__/helpers/fs/listFiles.test.ts | 56 ++ src/__tests__/helpers/fs/readFile.test.ts | 43 + .../helpers/template/compileTemplate.test.ts | 65 ++ .../helpers/template/isTemplateFile.test.ts | 22 + .../helpers/template/listTemplates.test.ts | 52 ++ .../template/processTemplateFolder.test.ts | 178 ++++ .../template/removeTemplateExtension.test.ts | 26 + .../template/renameTemplateFile.test.ts | 86 ++ .../template/resolveTargetPath.test.ts | 109 +++ .../template/walkTemplateFolder.test.ts | 98 +++ .../vscode/getWorkspaceFolders.test.ts | 59 ++ .../helpers/vscode/promptInput.test.ts | 58 ++ .../helpers/vscode/promptSelection.test.ts | 55 ++ .../helpers/vscode/showError.test.ts | 21 + src/__tests__/helpers/vscode/showInfo.test.ts | 21 + .../vscode/workspace/createFolder.test.ts | 43 + .../vscode/workspace/fileExists.test.ts | 29 + .../vscode/workspace/listEntries.test.ts | 80 ++ .../helpers/vscode/workspace/readFile.test.ts | 53 ++ .../vscode/workspace/writeFile.test.ts | 61 ++ .../utils/formatToPascalCase.test.ts | 72 ++ src/__tests__/utils/validation.test.ts | 88 ++ src/commands/createComponent.ts | 411 +++------ src/commands/index.ts | 2 - src/commands/updateComponent.ts | 410 +++------ src/constants/index.ts | 22 - src/errors/CodebotError.ts | 135 --- src/errors/index.ts | 1 - src/extension.ts | 9 +- src/helpers/buildTemplate.ts | 11 - src/helpers/checkExistsFile.ts | 10 - src/helpers/checkExistsFolder.ts | 5 - src/helpers/config/readConfig.ts | 16 + src/helpers/createFile.ts | 5 - src/helpers/createFolder.ts | 5 - src/helpers/filterTemplatesFiles.ts | 3 - src/helpers/formatTemplateName.ts | 11 - src/helpers/formatToPascalCase.ts | 6 - src/helpers/fs/createFile.ts | 23 + src/helpers/fs/createFolder.ts | 18 + src/helpers/fs/fileExists.ts | 10 + src/helpers/fs/folderExists.ts | 9 + src/helpers/fs/index.ts | 6 + src/helpers/fs/listFiles.ts | 17 + src/helpers/fs/readFile.ts | 13 + src/helpers/getConfigurationFile.ts | 24 - src/helpers/getFilesByFolder.ts | 5 - src/helpers/getTextByInputBox.ts | 8 - src/helpers/getWorkspaceFolder.ts | 5 - src/helpers/index.ts | 18 +- src/helpers/readFile.ts | 5 - src/helpers/showMessage.ts | 11 - src/helpers/showSearchDropdown.ts | 13 - src/helpers/template/compileTemplate.ts | 17 + src/helpers/template/index.ts | 5 + src/helpers/template/isTemplateFile.ts | 2 + src/helpers/template/listTemplates.ts | 18 + src/helpers/template/processTemplateFolder.ts | 60 ++ .../template/removeTemplateExtension.ts | 2 + src/helpers/template/renameTemplateFile.ts | 23 + src/helpers/template/resolveTargetPath.ts | 31 + src/helpers/template/walkTemplateFolder.ts | 52 ++ src/helpers/vscode/getWorkspaceFolders.ts | 18 + src/helpers/vscode/index.ts | 16 + src/helpers/vscode/promptInput.ts | 21 + src/helpers/vscode/promptSelection.ts | 17 + src/helpers/vscode/showError.ts | 5 + src/helpers/vscode/showInfo.ts | 5 + src/helpers/vscode/workspace/createFolder.ts | 20 + src/helpers/vscode/workspace/fileExists.ts | 12 + src/helpers/vscode/workspace/index.ts | 6 + src/helpers/vscode/workspace/listEntries.ts | 37 + src/helpers/vscode/workspace/readFile.ts | 22 + src/helpers/vscode/workspace/writeFile.ts | 23 + src/interfaces/index.ts | 72 -- src/managers/ConfigurationManager.ts | 218 ----- src/managers/FileSystemManager.ts | 265 ------ src/managers/ProjectDetector.ts | 169 ---- src/managers/TemplateManager.ts | 690 --------------- src/managers/index.ts | 4 - src/test/__mocks__/vscode.js | 51 -- src/test/commands/updateComponent.test.ts | 288 ------- src/test/fixtures/index.ts | 61 -- src/test/managers/FileSystemManager.test.ts | 375 --------- src/test/managers/TemplateManager.test.ts | 295 ------- src/test/setup.ts | 12 - .../unit/commands/updateComponent.test.ts | 297 ------- .../managers/ConfigurationManager.test.ts | 274 ------ .../unit/managers/ProjectDetector.test.ts | 213 ----- .../unit/managers/TemplateManager.test.ts | 796 ------------------ .../managers/TemplateManagerNaming.test.ts | 237 ------ .../utils/PathResolver.integration.test.ts | 298 ------- src/test/unit/utils/PathResolver.test.ts | 382 --------- src/test/utils/DirectoryProcessor.test.ts | 244 ------ src/types/AppError.ts | 29 + src/types/Config.ts | 7 + src/types/Option.ts | 18 + src/types/Result.ts | 30 + src/types/index.ts | 140 +-- src/utils/DirectoryProcessor.ts | 135 --- src/utils/PathResolver.ts | 326 ------- src/utils/PathResolverExample.ts | 0 src/utils/formatToPascalCase.ts | 13 + src/utils/index.ts | 2 - src/utils/validation.ts | 35 +- 115 files changed, 3333 insertions(+), 6850 deletions(-) delete mode 100644 CLAUDE.md create mode 100644 src/__mocks__/vscode.js create mode 100644 src/__tests__/commands/createComponent.test.ts create mode 100644 src/__tests__/commands/updateComponent.test.ts create mode 100644 src/__tests__/helpers/config/readConfig.test.ts create mode 100644 src/__tests__/helpers/fs/createFile.test.ts create mode 100644 src/__tests__/helpers/fs/createFolder.test.ts create mode 100644 src/__tests__/helpers/fs/fileExists.test.ts create mode 100644 src/__tests__/helpers/fs/folderExists.test.ts create mode 100644 src/__tests__/helpers/fs/listFiles.test.ts create mode 100644 src/__tests__/helpers/fs/readFile.test.ts create mode 100644 src/__tests__/helpers/template/compileTemplate.test.ts create mode 100644 src/__tests__/helpers/template/isTemplateFile.test.ts create mode 100644 src/__tests__/helpers/template/listTemplates.test.ts create mode 100644 src/__tests__/helpers/template/processTemplateFolder.test.ts create mode 100644 src/__tests__/helpers/template/removeTemplateExtension.test.ts create mode 100644 src/__tests__/helpers/template/renameTemplateFile.test.ts create mode 100644 src/__tests__/helpers/template/resolveTargetPath.test.ts create mode 100644 src/__tests__/helpers/template/walkTemplateFolder.test.ts create mode 100644 src/__tests__/helpers/vscode/getWorkspaceFolders.test.ts create mode 100644 src/__tests__/helpers/vscode/promptInput.test.ts create mode 100644 src/__tests__/helpers/vscode/promptSelection.test.ts create mode 100644 src/__tests__/helpers/vscode/showError.test.ts create mode 100644 src/__tests__/helpers/vscode/showInfo.test.ts create mode 100644 src/__tests__/helpers/vscode/workspace/createFolder.test.ts create mode 100644 src/__tests__/helpers/vscode/workspace/fileExists.test.ts create mode 100644 src/__tests__/helpers/vscode/workspace/listEntries.test.ts create mode 100644 src/__tests__/helpers/vscode/workspace/readFile.test.ts create mode 100644 src/__tests__/helpers/vscode/workspace/writeFile.test.ts create mode 100644 src/__tests__/utils/formatToPascalCase.test.ts create mode 100644 src/__tests__/utils/validation.test.ts delete mode 100644 src/commands/index.ts delete mode 100644 src/constants/index.ts delete mode 100644 src/errors/CodebotError.ts delete mode 100644 src/errors/index.ts delete mode 100644 src/helpers/buildTemplate.ts delete mode 100644 src/helpers/checkExistsFile.ts delete mode 100644 src/helpers/checkExistsFolder.ts create mode 100644 src/helpers/config/readConfig.ts delete mode 100644 src/helpers/createFile.ts delete mode 100644 src/helpers/createFolder.ts delete mode 100644 src/helpers/filterTemplatesFiles.ts delete mode 100644 src/helpers/formatTemplateName.ts delete mode 100644 src/helpers/formatToPascalCase.ts create mode 100644 src/helpers/fs/createFile.ts create mode 100644 src/helpers/fs/createFolder.ts create mode 100644 src/helpers/fs/fileExists.ts create mode 100644 src/helpers/fs/folderExists.ts create mode 100644 src/helpers/fs/index.ts create mode 100644 src/helpers/fs/listFiles.ts create mode 100644 src/helpers/fs/readFile.ts delete mode 100644 src/helpers/getConfigurationFile.ts delete mode 100644 src/helpers/getFilesByFolder.ts delete mode 100644 src/helpers/getTextByInputBox.ts delete mode 100644 src/helpers/getWorkspaceFolder.ts delete mode 100644 src/helpers/readFile.ts delete mode 100644 src/helpers/showMessage.ts delete mode 100644 src/helpers/showSearchDropdown.ts create mode 100644 src/helpers/template/compileTemplate.ts create mode 100644 src/helpers/template/index.ts create mode 100644 src/helpers/template/isTemplateFile.ts create mode 100644 src/helpers/template/listTemplates.ts create mode 100644 src/helpers/template/processTemplateFolder.ts create mode 100644 src/helpers/template/removeTemplateExtension.ts create mode 100644 src/helpers/template/renameTemplateFile.ts create mode 100644 src/helpers/template/resolveTargetPath.ts create mode 100644 src/helpers/template/walkTemplateFolder.ts create mode 100644 src/helpers/vscode/getWorkspaceFolders.ts create mode 100644 src/helpers/vscode/index.ts create mode 100644 src/helpers/vscode/promptInput.ts create mode 100644 src/helpers/vscode/promptSelection.ts create mode 100644 src/helpers/vscode/showError.ts create mode 100644 src/helpers/vscode/showInfo.ts create mode 100644 src/helpers/vscode/workspace/createFolder.ts create mode 100644 src/helpers/vscode/workspace/fileExists.ts create mode 100644 src/helpers/vscode/workspace/index.ts create mode 100644 src/helpers/vscode/workspace/listEntries.ts create mode 100644 src/helpers/vscode/workspace/readFile.ts create mode 100644 src/helpers/vscode/workspace/writeFile.ts delete mode 100644 src/interfaces/index.ts delete mode 100644 src/managers/ConfigurationManager.ts delete mode 100644 src/managers/FileSystemManager.ts delete mode 100644 src/managers/ProjectDetector.ts delete mode 100644 src/managers/TemplateManager.ts delete mode 100644 src/managers/index.ts delete mode 100644 src/test/__mocks__/vscode.js delete mode 100644 src/test/commands/updateComponent.test.ts delete mode 100644 src/test/fixtures/index.ts delete mode 100644 src/test/managers/FileSystemManager.test.ts delete mode 100644 src/test/managers/TemplateManager.test.ts delete mode 100644 src/test/setup.ts delete mode 100644 src/test/unit/commands/updateComponent.test.ts delete mode 100644 src/test/unit/managers/ConfigurationManager.test.ts delete mode 100644 src/test/unit/managers/ProjectDetector.test.ts delete mode 100644 src/test/unit/managers/TemplateManager.test.ts delete mode 100644 src/test/unit/managers/TemplateManagerNaming.test.ts delete mode 100644 src/test/unit/utils/PathResolver.integration.test.ts delete mode 100644 src/test/unit/utils/PathResolver.test.ts delete mode 100644 src/test/utils/DirectoryProcessor.test.ts create mode 100644 src/types/AppError.ts create mode 100644 src/types/Config.ts create mode 100644 src/types/Option.ts create mode 100644 src/types/Result.ts delete mode 100644 src/utils/DirectoryProcessor.ts delete mode 100644 src/utils/PathResolver.ts delete mode 100644 src/utils/PathResolverExample.ts create mode 100644 src/utils/formatToPascalCase.ts delete mode 100644 src/utils/index.ts diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 27529f7..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,80 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Commands - -```bash -npm run compile # Webpack build (development) -npm run watch # Webpack watch mode -npm run package # Production build with hidden source maps -npm run build # Package extension as .vsix -npm run lint # Biome check with auto-fix -npm run lint:check # Biome check without fixing (used in CI) -npm test # Run Jest tests -npm run test:watch # Jest watch mode -npm run test:coverage # Generate coverage reports -npm run dev-install # Compile, package, and install extension locally -``` - -To run a single test file: -```bash -npx jest src/test/path/to/file.test.ts -``` - -## Architecture - -Codebot is a VSCode extension that generates boilerplate code from Handlebars templates. Users right-click a folder in the Explorer, pick a template, enter a component name, and files are generated with `{{name}}` replaced by the component name (PascalCase). - -### Entry point - -`src/extension.ts` — registers two VSCode commands: `codebot.createComponent` and `codebot.updateComponent`. - -### Core flow - -1. **Command** (`src/commands/`) receives the folder URI from VSCode context -2. **ProjectDetector** (`src/managers/`) determines the project context (single vs. multi-workspace) -3. **ConfigurationManager** reads `codebot.config.json` for custom template paths -4. **TemplateManager** discovers `.hbs` template files and presents choices to the user -5. **Helpers** (`src/helpers/`) wrap VSCode UI (QuickPick, InputBox, file creation) -6. Template files are compiled via Handlebars and written to disk - -### Key modules - -- `src/managers/` — Core business logic. `TemplateManager` uses TTL-based caching and supports hierarchical template discovery. Managers accept dependencies via constructor injection. -- `src/commands/` — Thin handlers that orchestrate managers; `createComponent` generates new files, `updateComponent` adds missing template files to an existing component. -- `src/utils/` — `PathResolver` sanitizes and validates paths (prevents traversal attacks). `DirectoryProcessor` scans workspace directories. -- `src/errors/` — `CodebotError` typed error class with error category enum. -- `src/interfaces/` — TypeScript contracts for all managers and core data types (`ITemplateManager`, `IProjectDetector`, etc.). - -### Template structure (user-created) - -``` -project-root/ -├── templates/ -│ └── ComponentName/ -│ ├── ComponentName.tsx.hbs -│ └── index.tsx.hbs -└── codebot.config.json # optional, sets custom templatesFolderPath -``` - -Filenames containing the template folder name (or the literal string "Template") are renamed to the component name at generation time. - -## Build & packaging - -Webpack bundles everything into a single `dist/extension.js`. The `vscode` module is marked as external (provided by the host). TypeScript targets ES6 with strict mode enabled. - -## Linting & formatting - -[Biome](https://biomejs.dev/) replaces ESLint and Prettier. Config lives in `biome.json` at the project root. Rules follow Biome's recommended set; formatting matches the previous Prettier config (single quotes, 2-space indent, trailing commas, LF line endings). - -- `npm run lint:check` — check only, exits non-zero on warnings (used in CI) -- `npm run lint` — check and auto-fix - -## Testing - -Jest with `ts-jest`. The `vscode` module is mocked globally (see `src/test/__mocks__/vscode.ts`). Tests live alongside source in `src/test/`. - -## Release - -Semantic Release runs on pushes to `main` and publishes to the VS Marketplace. Commit messages must follow Conventional Commits (`feat:`, `fix:`, `chore:`, etc.) to trigger correct version bumps. diff --git a/package.json b/package.json index 9d557cd..2e8c037 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "codebot", "displayName": "Codebot - Code generator by templates", "description": "This extension creates automated codes based on custom created templates.", - "version": "1.0.0", + "version": "2.0.0", "icon": "assets/codebot.png", "repository": { "type": "git", @@ -17,6 +17,16 @@ ], "main": "./dist/extension.js", "contributes": { + "configuration": { + "title": "Codebot", + "properties": { + "codebot.templatesFolderPath": { + "type": "string", + "default": "templates", + "markdownDescription": "Path to the templates folder, relative to the workspace root." + } + } + }, "commands": [ { "command": "codebot.createComponent", diff --git a/src/__mocks__/vscode.js b/src/__mocks__/vscode.js new file mode 100644 index 0000000..c2d7033 --- /dev/null +++ b/src/__mocks__/vscode.js @@ -0,0 +1,49 @@ +const path = require('node:path'); + +const makeUri = fsPath => ({ + fsPath, + scheme: 'file', + path: fsPath, + toString: () => `file://${fsPath}`, +}); + +const Uri = { + file: fsPath => makeUri(fsPath), + joinPath: (base, ...segments) => makeUri(path.join(base.fsPath, ...segments)), +}; + +const workspace = { + fs: { + readDirectory: jest.fn(), + readFile: jest.fn(), + stat: jest.fn(), + createDirectory: jest.fn(), + writeFile: jest.fn(), + }, + getConfiguration: jest.fn(), + workspaceFolders: [ + { + uri: { fsPath: '/test/workspace' }, + name: 'workspace', + index: 0, + }, + ], +}; + +const window = { + showErrorMessage: jest.fn(), + showInformationMessage: jest.fn(), + showQuickPick: jest.fn(), + showInputBox: jest.fn(), +}; + +const commands = { + registerCommand: jest.fn(), +}; + +module.exports = { + Uri, + workspace, + window, + commands, +}; diff --git a/src/__tests__/commands/createComponent.test.ts b/src/__tests__/commands/createComponent.test.ts new file mode 100644 index 0000000..a0f3bfd --- /dev/null +++ b/src/__tests__/commands/createComponent.test.ts @@ -0,0 +1,428 @@ +import * as vscode from 'vscode'; +import { createComponent } from '../../commands/createComponent'; + +const mockFs = vscode.workspace.fs as jest.Mocked; +const mockWindow = vscode.window as jest.Mocked; +const mockGetConfiguration = vscode.workspace + .getConfiguration as jest.MockedFunction< + typeof vscode.workspace.getConfiguration +>; + +const encode = (s: string) => new TextEncoder().encode(s); + +const clickedUri = vscode.Uri.file('/test/workspace/src/components'); + +const setupConfig = (templatesFolderPath = 'templates') => { + mockGetConfiguration.mockReturnValue({ + get: jest.fn().mockReturnValue(templatesFolderPath), + update: jest.fn(), + has: jest.fn(), + inspect: jest.fn(), + } as unknown as vscode.WorkspaceConfiguration); +}; + +const setupSingleTemplate = ( + templateName: string, + files: [string, number][], +) => { + mockFs.readDirectory.mockResolvedValueOnce([[templateName, 2]] as never); + mockFs.readDirectory.mockResolvedValueOnce(files as never); +}; + +describe('createComponent', () => { + beforeEach(() => { + jest.resetAllMocks(); + setupConfig(); + }); + + describe('guard clauses', () => { + it('shows error and returns when no URI is provided', async () => { + await createComponent(undefined); + + expect(mockWindow.showErrorMessage).toHaveBeenCalledWith( + expect.stringContaining('Right-click'), + ); + expect(mockFs.writeFile).not.toHaveBeenCalled(); + }); + + it('shows error when workspace folder is not found', async () => { + const outsideUri = vscode.Uri.file('/outside/any/workspace'); + + await createComponent(outsideUri); + + expect(mockWindow.showErrorMessage).toHaveBeenCalledWith( + expect.stringContaining('workspace folder'), + ); + }); + + it('shows error when templates folder cannot be read', async () => { + mockFs.readDirectory.mockRejectedValue(new Error('ENOENT')); + + await createComponent(clickedUri); + + expect(mockWindow.showErrorMessage).toHaveBeenCalledWith( + expect.stringContaining('templates'), + ); + expect(mockFs.writeFile).not.toHaveBeenCalled(); + }); + + it('shows error when no templates are found', async () => { + mockFs.readDirectory.mockResolvedValue([['README.md', 1]] as never); + + await createComponent(clickedUri); + + expect(mockWindow.showErrorMessage).toHaveBeenCalledWith( + expect.stringContaining('No templates found'), + ); + }); + + it('returns silently when user cancels template selection', async () => { + mockFs.readDirectory.mockResolvedValueOnce([ + ['ComponentSass', 2], + ['ComponentTailwind', 2], + ] as never); + mockWindow.showQuickPick.mockResolvedValue(undefined); + + await createComponent(clickedUri); + + expect(mockWindow.showErrorMessage).not.toHaveBeenCalled(); + expect(mockWindow.showInformationMessage).not.toHaveBeenCalled(); + }); + + it('returns silently when user cancels component name input', async () => { + setupSingleTemplate('ComponentSass', []); + mockWindow.showInputBox.mockResolvedValue(undefined); + + await createComponent(clickedUri); + + expect(mockWindow.showErrorMessage).not.toHaveBeenCalled(); + expect(mockWindow.showInformationMessage).not.toHaveBeenCalled(); + }); + }); + + describe('happy path — single template', () => { + beforeEach(() => { + mockFs.readDirectory + .mockResolvedValueOnce([['ComponentSass', 2]] as never) + .mockResolvedValueOnce([ + ['ComponentSass.tsx.hbs', 1], + ['index.tsx.hbs', 1], + ] as never); + + mockFs.readFile + .mockResolvedValueOnce( + encode('export const {{name}} = () => null;') as never, + ) + .mockResolvedValueOnce( + encode("export { {{name}} } from './{{name}}';") as never, + ); + + mockFs.stat.mockRejectedValue(new Error('not found')); + mockFs.createDirectory.mockResolvedValue(undefined); + mockFs.writeFile.mockResolvedValue(undefined); + + mockWindow.showInputBox.mockResolvedValue('Button'); + }); + + it('auto-selects the only template without showing QuickPick', async () => { + await createComponent(clickedUri); + + expect(mockWindow.showQuickPick).not.toHaveBeenCalled(); + }); + + it('writes all template files to the target folder', async () => { + await createComponent(clickedUri); + + expect(mockFs.writeFile).toHaveBeenCalledTimes(2); + }); + + it('writes files at the correct URIs', async () => { + await createComponent(clickedUri); + + const writtenPaths = mockFs.writeFile.mock.calls.map( + ([uri]) => (uri as vscode.Uri).fsPath, + ); + expect(writtenPaths.some(p => p.endsWith('Button/Button.tsx'))).toBe( + true, + ); + expect(writtenPaths.some(p => p.endsWith('Button/index.tsx'))).toBe(true); + }); + + it('compiles the component name into template content', async () => { + await createComponent(clickedUri); + + const found = mockFs.writeFile.mock.calls.find(([uri]) => + (uri as vscode.Uri).fsPath.endsWith('Button.tsx'), + ); + expect(found).toBeDefined(); + const [, content] = found ?? []; + + const text = new TextDecoder().decode(content as Uint8Array); + expect(text).toBe('export const Button = () => null;'); + }); + + it('shows success message', async () => { + await createComponent(clickedUri); + + expect(mockWindow.showInformationMessage).toHaveBeenCalledWith( + expect.stringContaining("'Button' created"), + ); + }); + }); + + describe('happy path — multiple templates', () => { + it('shows QuickPick and uses the selected template', async () => { + mockFs.readDirectory + .mockResolvedValueOnce([ + ['ComponentSass', 2], + ['ComponentTailwind', 2], + ] as never) + .mockResolvedValueOnce([['ComponentTailwind.tsx.hbs', 1]] as never); + + mockFs.readFile.mockResolvedValue(encode('// {{name}}') as never); + mockFs.stat.mockRejectedValue(new Error('not found')); + mockFs.createDirectory.mockResolvedValue(undefined); + mockFs.writeFile.mockResolvedValue(undefined); + + mockWindow.showQuickPick.mockResolvedValue('ComponentTailwind' as never); + mockWindow.showInputBox.mockResolvedValue('Card'); + + await createComponent(clickedUri); + + expect(mockWindow.showQuickPick).toHaveBeenCalledWith( + ['ComponentSass', 'ComponentTailwind'], + expect.anything(), + ); + expect(mockWindow.showInformationMessage).toHaveBeenCalledWith( + expect.stringContaining("'Card' created"), + ); + }); + }); + + describe('error scenarios', () => { + it('shows error when template processing fails (e.g. unreadable .hbs file)', async () => { + mockFs.readDirectory + .mockResolvedValueOnce([['ComponentSass', 2]] as never) + .mockResolvedValueOnce([['ComponentSass.tsx.hbs', 1]] as never); + + mockFs.readFile.mockRejectedValue(new Error('Permission denied')); + + mockWindow.showInputBox.mockResolvedValue('Button'); + + await createComponent(clickedUri); + + expect(mockWindow.showErrorMessage).toHaveBeenCalledWith( + expect.stringContaining('Failed to process template'), + ); + expect(mockFs.writeFile).not.toHaveBeenCalled(); + }); + + it('shows error when template folder nesting exceeds the depth limit', async () => { + // First call lists the template; subsequent calls always return a subdirectory, + // forcing walkTemplateFolder past MAX_DEPTH. + mockFs.readDirectory + .mockResolvedValueOnce([['ComponentSass', 2]] as never) + .mockResolvedValue([['nested', 2]] as never); + + mockWindow.showInputBox.mockResolvedValue('Button'); + + await createComponent(clickedUri); + + expect(mockWindow.showErrorMessage).toHaveBeenCalledWith( + expect.stringContaining('Failed to process template'), + ); + expect(mockFs.writeFile).not.toHaveBeenCalled(); + }); + + it('shows error when two template files resolve to the same output path', async () => { + // "ComponentSass.tsx.hbs" and "Template.tsx.hbs" both compile to "Button.tsx" + mockFs.readDirectory + .mockResolvedValueOnce([['ComponentSass', 2]] as never) + .mockResolvedValueOnce([ + ['ComponentSass.tsx.hbs', 1], + ['Template.tsx.hbs', 1], + ] as never); + + mockFs.readFile.mockResolvedValue(encode('// {{name}}') as never); + + mockWindow.showInputBox.mockResolvedValue('Button'); + + await createComponent(clickedUri); + + expect(mockWindow.showErrorMessage).toHaveBeenCalledWith( + expect.stringContaining('Failed to process template'), + ); + expect(mockFs.writeFile).not.toHaveBeenCalled(); + }); + + it('shows error and stops when folder creation fails', async () => { + mockFs.readDirectory + .mockResolvedValueOnce([['ComponentSass', 2]] as never) + .mockResolvedValueOnce([ + ['ComponentSass.tsx.hbs', 1], + ['index.tsx.hbs', 1], + ] as never); + + mockFs.readFile.mockResolvedValue(encode('// {{name}}') as never); + mockFs.stat.mockRejectedValue(new Error('not found')); + mockFs.createDirectory.mockRejectedValue(new Error('No permissions')); + + mockWindow.showInputBox.mockResolvedValue('Button'); + + await createComponent(clickedUri); + + expect(mockWindow.showErrorMessage).toHaveBeenCalledWith( + expect.stringContaining('Failed to create folder'), + ); + expect(mockFs.writeFile).not.toHaveBeenCalled(); + }); + + it('shows error and stops when file write fails', async () => { + mockFs.readDirectory + .mockResolvedValueOnce([['ComponentSass', 2]] as never) + .mockResolvedValueOnce([ + ['ComponentSass.tsx.hbs', 1], + ['index.tsx.hbs', 1], + ] as never); + + mockFs.readFile.mockResolvedValue(encode('// {{name}}') as never); + mockFs.stat.mockRejectedValue(new Error('not found')); + mockFs.createDirectory.mockResolvedValue(undefined); + mockFs.writeFile.mockRejectedValue(new Error('Disk full')); + + mockWindow.showInputBox.mockResolvedValue('Button'); + + await createComponent(clickedUri); + + expect(mockWindow.showErrorMessage).toHaveBeenCalledWith( + expect.stringContaining('Failed to write'), + ); + expect(mockFs.writeFile).toHaveBeenCalledTimes(1); + }); + + it('does not write remaining files after the first write failure', async () => { + mockFs.readDirectory + .mockResolvedValueOnce([['ComponentSass', 2]] as never) + .mockResolvedValueOnce([ + ['ComponentSass.tsx.hbs', 1], + ['index.tsx.hbs', 1], + ] as never); + + mockFs.readFile.mockResolvedValue(encode('// {{name}}') as never); + mockFs.stat.mockRejectedValue(new Error('not found')); + mockFs.createDirectory.mockResolvedValue(undefined); + mockFs.writeFile + .mockRejectedValueOnce(new Error('Disk full')) + .mockResolvedValue(undefined); + + mockWindow.showInputBox.mockResolvedValue('Button'); + + await createComponent(clickedUri); + + expect(mockFs.writeFile).toHaveBeenCalledTimes(1); + expect(mockWindow.showInformationMessage).not.toHaveBeenCalled(); + }); + }); + + describe('file skipping', () => { + it('skips files that already exist and reports them', async () => { + mockFs.readDirectory + .mockResolvedValueOnce([['ComponentSass', 2]] as never) + .mockResolvedValueOnce([ + ['ComponentSass.tsx.hbs', 1], + ['index.tsx.hbs', 1], + ] as never); + + mockFs.readFile.mockResolvedValue(encode('// {{name}}') as never); + + // First file exists, second does not + mockFs.stat + .mockResolvedValueOnce({ + type: 1, + ctime: 0, + mtime: 0, + size: 1, + } as never) + .mockRejectedValueOnce(new Error('not found')); + + mockFs.createDirectory.mockResolvedValue(undefined); + mockFs.writeFile.mockResolvedValue(undefined); + + mockWindow.showInputBox.mockResolvedValue('Button'); + + await createComponent(clickedUri); + + expect(mockFs.writeFile).toHaveBeenCalledTimes(1); + expect(mockWindow.showInformationMessage).toHaveBeenCalledWith( + expect.stringContaining('skipped'), + ); + }); + }); + + describe('custom templates folder path', () => { + it('uses the configured templatesFolderPath', async () => { + setupConfig('src/templates'); + + mockFs.readDirectory + .mockResolvedValueOnce([['ComponentSass', 2]] as never) + .mockResolvedValueOnce([['ComponentSass.tsx.hbs', 1]] as never); + + mockFs.readFile.mockResolvedValue(encode('// {{name}}') as never); + mockFs.stat.mockRejectedValue(new Error('not found')); + mockFs.createDirectory.mockResolvedValue(undefined); + mockFs.writeFile.mockResolvedValue(undefined); + + mockWindow.showInputBox.mockResolvedValue('Button'); + + await createComponent(clickedUri); + + const readDirCalls = mockFs.readDirectory.mock.calls; + expect(readDirCalls[0][0].fsPath).toContain('src/templates'); + }); + }); + + describe('PascalCase formatting', () => { + const setupTemplate = () => { + mockFs.readDirectory + .mockResolvedValueOnce([['ComponentSass', 2]] as never) + .mockResolvedValueOnce([['ComponentSass.tsx.hbs', 1]] as never); + mockFs.readFile.mockResolvedValue(encode('// {{name}}') as never); + mockFs.stat.mockRejectedValue(new Error('not found')); + mockFs.createDirectory.mockResolvedValue(undefined); + mockFs.writeFile.mockResolvedValue(undefined); + }; + + it('converts lowercase input to PascalCase', async () => { + setupTemplate(); + mockWindow.showInputBox.mockResolvedValue('button'); + + await createComponent(clickedUri); + + expect(mockWindow.showInformationMessage).toHaveBeenCalledWith( + expect.stringContaining("'Button' created"), + ); + }); + + it('converts kebab-case input to PascalCase', async () => { + setupTemplate(); + mockWindow.showInputBox.mockResolvedValue('my-card'); + + await createComponent(clickedUri); + + expect(mockWindow.showInformationMessage).toHaveBeenCalledWith( + expect.stringContaining("'MyCard' created"), + ); + }); + + it('converts camelCase input to PascalCase', async () => { + setupTemplate(); + mockWindow.showInputBox.mockResolvedValue('myComponent'); + + await createComponent(clickedUri); + + expect(mockWindow.showInformationMessage).toHaveBeenCalledWith( + expect.stringContaining("'MyComponent' created"), + ); + }); + }); +}); diff --git a/src/__tests__/commands/updateComponent.test.ts b/src/__tests__/commands/updateComponent.test.ts new file mode 100644 index 0000000..43e114a --- /dev/null +++ b/src/__tests__/commands/updateComponent.test.ts @@ -0,0 +1,349 @@ +import * as vscode from 'vscode'; +import { updateComponent } from '../../commands/updateComponent'; + +const mockFs = vscode.workspace.fs as jest.Mocked; +const mockWindow = vscode.window as jest.Mocked; +const mockGetConfiguration = vscode.workspace + .getConfiguration as jest.MockedFunction< + typeof vscode.workspace.getConfiguration +>; + +const encode = (s: string) => new TextEncoder().encode(s); + +// Clicking directly on the component folder (e.g. src/components/Button) +const clickedUri = vscode.Uri.file('/test/workspace/src/components/Button'); + +const setupConfig = (templatesFolderPath = 'templates') => { + mockGetConfiguration.mockReturnValue({ + get: jest.fn().mockReturnValue(templatesFolderPath), + update: jest.fn(), + has: jest.fn(), + inspect: jest.fn(), + } as unknown as vscode.WorkspaceConfiguration); +}; + +describe('updateComponent', () => { + beforeEach(() => { + jest.resetAllMocks(); + setupConfig(); + }); + + describe('guard clauses', () => { + it('shows error when no URI is provided', async () => { + await updateComponent(undefined); + + expect(mockWindow.showErrorMessage).toHaveBeenCalledWith( + expect.stringContaining('Right-click'), + ); + expect(mockFs.writeFile).not.toHaveBeenCalled(); + }); + + it('shows error when clicked folder name is not a valid component name', async () => { + const invalidUri = vscode.Uri.file('/test/workspace/src/1invalid'); + + await updateComponent(invalidUri); + + expect(mockWindow.showErrorMessage).toHaveBeenCalledWith( + expect.stringContaining('1invalid'), + ); + expect(mockFs.writeFile).not.toHaveBeenCalled(); + }); + + it('shows error when workspace folder is not found', async () => { + const outsideUri = vscode.Uri.file('/outside/any/workspace/Button'); + + await updateComponent(outsideUri); + + expect(mockWindow.showErrorMessage).toHaveBeenCalledWith( + expect.stringContaining('workspace folder'), + ); + }); + + it('shows error when templates folder cannot be read', async () => { + mockFs.readDirectory.mockRejectedValue(new Error('ENOENT')); + + await updateComponent(clickedUri); + + expect(mockWindow.showErrorMessage).toHaveBeenCalledWith( + expect.stringContaining('templates'), + ); + expect(mockFs.writeFile).not.toHaveBeenCalled(); + }); + + it('shows error when no templates are found', async () => { + mockFs.readDirectory.mockResolvedValue([['README.md', 1]] as never); + + await updateComponent(clickedUri); + + expect(mockWindow.showErrorMessage).toHaveBeenCalledWith( + expect.stringContaining('No templates found'), + ); + }); + + it('returns silently when user cancels template selection', async () => { + mockFs.readDirectory.mockResolvedValueOnce([ + ['ComponentSass', 2], + ['ComponentTailwind', 2], + ] as never); + mockWindow.showQuickPick.mockResolvedValue(undefined); + + await updateComponent(clickedUri); + + expect(mockWindow.showErrorMessage).not.toHaveBeenCalled(); + expect(mockWindow.showInformationMessage).not.toHaveBeenCalled(); + }); + }); + + describe('happy path — uses folder name as component name', () => { + beforeEach(() => { + mockFs.readDirectory + .mockResolvedValueOnce([['ComponentSass', 2]] as never) + .mockResolvedValueOnce([ + ['ComponentSass.tsx.hbs', 1], + ['index.tsx.hbs', 1], + ] as never); + + mockFs.readFile + .mockResolvedValueOnce( + encode('export const {{name}} = () => null;') as never, + ) + .mockResolvedValueOnce( + encode("export { {{name}} } from './{{name}}';") as never, + ); + + mockFs.stat.mockRejectedValue(new Error('not found')); + mockFs.createDirectory.mockResolvedValue(undefined); + mockFs.writeFile.mockResolvedValue(undefined); + }); + + it('does not ask for a component name', async () => { + await updateComponent(clickedUri); + + expect(mockWindow.showInputBox).not.toHaveBeenCalled(); + }); + + it('writes files directly into the clicked folder, not a subfolder', async () => { + await updateComponent(clickedUri); + + const writtenPaths = mockFs.writeFile.mock.calls.map( + ([uri]) => (uri as vscode.Uri).fsPath, + ); + + // Parent of Button.tsx must be the clicked folder itself, not a nested subfolder + const nodePath = require('node:path'); + const buttonFile = writtenPaths.find(p => p.endsWith('Button.tsx')); + const indexFile = writtenPaths.find(p => p.endsWith('index.tsx')); + expect(buttonFile).toBeDefined(); + expect(indexFile).toBeDefined(); + expect(nodePath.dirname(buttonFile ?? '')).toBe(clickedUri.fsPath); + expect(nodePath.dirname(indexFile ?? '')).toBe(clickedUri.fsPath); + }); + + it('compiles the folder name as component name into template content', async () => { + await updateComponent(clickedUri); + + const found = mockFs.writeFile.mock.calls.find(([uri]) => + (uri as vscode.Uri).fsPath.endsWith('Button.tsx'), + ); + expect(found).toBeDefined(); + const [, content] = found ?? []; + + const text = new TextDecoder().decode(content as Uint8Array); + expect(text).toBe('export const Button = () => null;'); + }); + + it('shows success message with "updated"', async () => { + await updateComponent(clickedUri); + + expect(mockWindow.showInformationMessage).toHaveBeenCalledWith( + expect.stringContaining("'Button' updated"), + ); + }); + }); + + describe('skipping existing files', () => { + it('skips files that already exist', async () => { + mockFs.readDirectory + .mockResolvedValueOnce([['ComponentSass', 2]] as never) + .mockResolvedValueOnce([ + ['ComponentSass.tsx.hbs', 1], + ['index.tsx.hbs', 1], + ] as never); + + mockFs.readFile.mockResolvedValue(encode('// {{name}}') as never); + mockFs.stat + .mockResolvedValueOnce({ + type: 1, + ctime: 0, + mtime: 0, + size: 1, + } as never) + .mockRejectedValueOnce(new Error('not found')); + + mockFs.createDirectory.mockResolvedValue(undefined); + mockFs.writeFile.mockResolvedValue(undefined); + + await updateComponent(clickedUri); + + expect(mockFs.writeFile).toHaveBeenCalledTimes(1); + expect(mockWindow.showInformationMessage).toHaveBeenCalledWith( + expect.stringContaining('skipped'), + ); + }); + + it('shows "already up to date" when all files exist', async () => { + mockFs.readDirectory + .mockResolvedValueOnce([['ComponentSass', 2]] as never) + .mockResolvedValueOnce([ + ['ComponentSass.tsx.hbs', 1], + ['index.tsx.hbs', 1], + ] as never); + + mockFs.readFile.mockResolvedValue(encode('// {{name}}') as never); + mockFs.stat.mockResolvedValue({ + type: 1, + ctime: 0, + mtime: 0, + size: 1, + } as never); + + await updateComponent(clickedUri); + + expect(mockFs.writeFile).not.toHaveBeenCalled(); + expect(mockWindow.showInformationMessage).toHaveBeenCalledWith( + expect.stringContaining('already up to date'), + ); + }); + }); + + describe('PascalCase formatting', () => { + it('converts a kebab-case folder name to PascalCase', async () => { + const kebabUri = vscode.Uri.file( + '/test/workspace/src/components/my-button', + ); + + mockFs.readDirectory + .mockResolvedValueOnce([['ComponentSass', 2]] as never) + .mockResolvedValueOnce([['ComponentSass.tsx.hbs', 1]] as never); + + mockFs.readFile.mockResolvedValue(encode('// {{name}}') as never); + mockFs.stat.mockRejectedValue(new Error('not found')); + mockFs.createDirectory.mockResolvedValue(undefined); + mockFs.writeFile.mockResolvedValue(undefined); + + await updateComponent(kebabUri); + + expect(mockWindow.showInformationMessage).toHaveBeenCalledWith( + expect.stringContaining("'MyButton' updated"), + ); + }); + + it('converts snake_case folder name to PascalCase', async () => { + const snakeUri = vscode.Uri.file( + '/test/workspace/src/components/my_card', + ); + + mockFs.readDirectory + .mockResolvedValueOnce([['ComponentSass', 2]] as never) + .mockResolvedValueOnce([['ComponentSass.tsx.hbs', 1]] as never); + + mockFs.readFile.mockResolvedValue(encode('// {{name}}') as never); + mockFs.stat.mockRejectedValue(new Error('not found')); + mockFs.createDirectory.mockResolvedValue(undefined); + mockFs.writeFile.mockResolvedValue(undefined); + + await updateComponent(snakeUri); + + expect(mockWindow.showInformationMessage).toHaveBeenCalledWith( + expect.stringContaining("'MyCard' updated"), + ); + }); + }); + + describe('error scenarios', () => { + it('shows error when template processing fails', async () => { + mockFs.readDirectory + .mockResolvedValueOnce([['ComponentSass', 2]] as never) + .mockResolvedValueOnce([['ComponentSass.tsx.hbs', 1]] as never); + + mockFs.readFile.mockRejectedValue(new Error('Permission denied')); + + await updateComponent(clickedUri); + + expect(mockWindow.showErrorMessage).toHaveBeenCalledWith( + expect.stringContaining('Failed to process template'), + ); + expect(mockFs.writeFile).not.toHaveBeenCalled(); + }); + + it('shows error when template folder nesting exceeds the depth limit', async () => { + mockFs.readDirectory + .mockResolvedValueOnce([['ComponentSass', 2]] as never) + .mockResolvedValue([['nested', 2]] as never); + + await updateComponent(clickedUri); + + expect(mockWindow.showErrorMessage).toHaveBeenCalledWith( + expect.stringContaining('Failed to process template'), + ); + expect(mockFs.writeFile).not.toHaveBeenCalled(); + }); + + it('shows error when two template files resolve to the same output path', async () => { + mockFs.readDirectory + .mockResolvedValueOnce([['ComponentSass', 2]] as never) + .mockResolvedValueOnce([ + ['ComponentSass.tsx.hbs', 1], + ['Template.tsx.hbs', 1], + ] as never); + + mockFs.readFile.mockResolvedValue(encode('// {{name}}') as never); + + await updateComponent(clickedUri); + + expect(mockWindow.showErrorMessage).toHaveBeenCalledWith( + expect.stringContaining('Failed to process template'), + ); + expect(mockFs.writeFile).not.toHaveBeenCalled(); + }); + + it('shows error and stops when folder creation fails', async () => { + mockFs.readDirectory + .mockResolvedValueOnce([['ComponentSass', 2]] as never) + .mockResolvedValueOnce([['ComponentSass.tsx.hbs', 1]] as never); + + mockFs.readFile.mockResolvedValue(encode('// {{name}}') as never); + mockFs.stat.mockRejectedValue(new Error('not found')); + mockFs.createDirectory.mockRejectedValue(new Error('No permissions')); + + await updateComponent(clickedUri); + + expect(mockWindow.showErrorMessage).toHaveBeenCalledWith( + expect.stringContaining('Failed to create folder'), + ); + expect(mockFs.writeFile).not.toHaveBeenCalled(); + }); + + it('shows error and stops when file write fails', async () => { + mockFs.readDirectory + .mockResolvedValueOnce([['ComponentSass', 2]] as never) + .mockResolvedValueOnce([ + ['ComponentSass.tsx.hbs', 1], + ['index.tsx.hbs', 1], + ] as never); + + mockFs.readFile.mockResolvedValue(encode('// {{name}}') as never); + mockFs.stat.mockRejectedValue(new Error('not found')); + mockFs.createDirectory.mockResolvedValue(undefined); + mockFs.writeFile.mockRejectedValue(new Error('Disk full')); + + await updateComponent(clickedUri); + + expect(mockWindow.showErrorMessage).toHaveBeenCalledWith( + expect.stringContaining('Failed to write'), + ); + expect(mockFs.writeFile).toHaveBeenCalledTimes(1); + expect(mockWindow.showInformationMessage).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/src/__tests__/helpers/config/readConfig.test.ts b/src/__tests__/helpers/config/readConfig.test.ts new file mode 100644 index 0000000..f718f07 --- /dev/null +++ b/src/__tests__/helpers/config/readConfig.test.ts @@ -0,0 +1,75 @@ +import * as vscode from 'vscode'; +import { readConfig } from '../../../helpers/config/readConfig'; + +const mockGetConfiguration = vscode.workspace + .getConfiguration as jest.MockedFunction< + typeof vscode.workspace.getConfiguration +>; + +const mockGet = jest.fn(); + +beforeEach(() => { + jest.clearAllMocks(); + mockGetConfiguration.mockReturnValue({ + get: mockGet, + update: jest.fn(), + has: jest.fn(), + inspect: jest.fn(), + } as unknown as vscode.WorkspaceConfiguration); +}); + +describe('readConfig', () => { + describe('templatesFolderPath', () => { + it('returns the configured value when set', () => { + mockGet.mockReturnValue('custom/templates'); + + const config = readConfig(); + + expect(config.templatesFolderPath).toBe('custom/templates'); + }); + + it('returns default "templates" when setting is not defined', () => { + mockGet.mockReturnValue(undefined); + + const config = readConfig(); + + expect(config.templatesFolderPath).toBe('templates'); + }); + + it('calls getConfiguration with "codebot" section', () => { + mockGet.mockReturnValue('templates'); + + readConfig(); + + expect(mockGetConfiguration).toHaveBeenCalledWith('codebot', undefined); + }); + + it('passes scope URI to getConfiguration when provided', () => { + const uri = vscode.Uri.file('/workspace/my-package'); + mockGet.mockReturnValue('templates'); + + readConfig(uri); + + expect(mockGetConfiguration).toHaveBeenCalledWith('codebot', uri); + }); + + it('reads the correct key from the configuration', () => { + mockGet.mockReturnValue('src/templates'); + + readConfig(); + + expect(mockGet).toHaveBeenCalledWith('templatesFolderPath'); + }); + }); + + describe('return shape', () => { + it('returns a frozen-compatible plain object', () => { + mockGet.mockReturnValue('templates'); + + const config = readConfig(); + + expect(config).toEqual({ templatesFolderPath: 'templates' }); + expect(typeof config).toBe('object'); + }); + }); +}); diff --git a/src/__tests__/helpers/fs/createFile.test.ts b/src/__tests__/helpers/fs/createFile.test.ts new file mode 100644 index 0000000..5236116 --- /dev/null +++ b/src/__tests__/helpers/fs/createFile.test.ts @@ -0,0 +1,73 @@ +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { createFile } from '../../../helpers/fs/createFile'; + +describe('createFile', () => { + afterEach(() => jest.restoreAllMocks()); + + it('creates parent directories and writes the file', () => { + const mkdirSpy = jest.spyOn(fs, 'mkdirSync').mockReturnValue(undefined); + const writeSpy = jest.spyOn(fs, 'writeFileSync').mockReturnValue(undefined); + + const result = createFile( + '/project/src/Button/Button.tsx', + 'export default function Button() {}', + ); + + expect(result).toEqual({ success: true, value: undefined }); + expect(mkdirSpy).toHaveBeenCalledWith( + path.dirname('/project/src/Button/Button.tsx'), + { + recursive: true, + }, + ); + expect(writeSpy).toHaveBeenCalledWith( + '/project/src/Button/Button.tsx', + 'export default function Button() {}', + 'utf-8', + ); + }); + + it('returns FILE_WRITE_ERROR when writeFileSync throws', () => { + jest.spyOn(fs, 'mkdirSync').mockReturnValue(undefined); + jest.spyOn(fs, 'writeFileSync').mockImplementation(() => { + throw new Error('Permission denied'); + }); + + const result = createFile('/locked/file.ts', 'content'); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.code).toBe('FILE_WRITE_ERROR'); + expect(result.error.message).toContain('/locked/file.ts'); + } + }); + + it('returns FILE_WRITE_ERROR when mkdirSync throws', () => { + jest.spyOn(fs, 'mkdirSync').mockImplementation(() => { + throw new Error('Read-only filesystem'); + }); + + const result = createFile('/readonly/dir/file.ts', 'content'); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.code).toBe('FILE_WRITE_ERROR'); + } + }); + + it('preserves the original error as cause', () => { + const originalError = new Error('EACCES'); + jest.spyOn(fs, 'mkdirSync').mockReturnValue(undefined); + jest.spyOn(fs, 'writeFileSync').mockImplementation(() => { + throw originalError; + }); + + const result = createFile('/file.ts', ''); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.cause).toBe(originalError); + } + }); +}); diff --git a/src/__tests__/helpers/fs/createFolder.test.ts b/src/__tests__/helpers/fs/createFolder.test.ts new file mode 100644 index 0000000..440c632 --- /dev/null +++ b/src/__tests__/helpers/fs/createFolder.test.ts @@ -0,0 +1,45 @@ +import * as fs from 'node:fs'; +import { createFolder } from '../../../helpers/fs/createFolder'; + +describe('createFolder', () => { + afterEach(() => jest.restoreAllMocks()); + + it('creates folder with recursive option', () => { + const mkdirSpy = jest.spyOn(fs, 'mkdirSync').mockReturnValue(undefined); + + const result = createFolder('/project/src/components/Button'); + + expect(result).toEqual({ success: true, value: undefined }); + expect(mkdirSpy).toHaveBeenCalledWith('/project/src/components/Button', { + recursive: true, + }); + }); + + it('returns FOLDER_CREATE_ERROR when mkdirSync throws', () => { + jest.spyOn(fs, 'mkdirSync').mockImplementation(() => { + throw new Error('EACCES: permission denied'); + }); + + const result = createFolder('/readonly/dir'); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.code).toBe('FOLDER_CREATE_ERROR'); + expect(result.error.message).toContain('/readonly/dir'); + } + }); + + it('preserves the original error as cause', () => { + const originalError = new Error('EACCES'); + jest.spyOn(fs, 'mkdirSync').mockImplementation(() => { + throw originalError; + }); + + const result = createFolder('/dir'); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.cause).toBe(originalError); + } + }); +}); diff --git a/src/__tests__/helpers/fs/fileExists.test.ts b/src/__tests__/helpers/fs/fileExists.test.ts new file mode 100644 index 0000000..c495be3 --- /dev/null +++ b/src/__tests__/helpers/fs/fileExists.test.ts @@ -0,0 +1,32 @@ +import * as fs from 'node:fs'; +import { fileExists } from '../../../helpers/fs/fileExists'; + +describe('fileExists', () => { + afterEach(() => jest.restoreAllMocks()); + + it('returns true when file is accessible', () => { + jest.spyOn(fs, 'accessSync').mockReturnValue(undefined); + + expect(fileExists('/project/file.ts')).toBe(true); + expect(fs.accessSync).toHaveBeenCalledWith( + '/project/file.ts', + fs.constants.F_OK, + ); + }); + + it('returns false when file does not exist', () => { + jest.spyOn(fs, 'accessSync').mockImplementation(() => { + throw new Error('ENOENT'); + }); + + expect(fileExists('/missing/file.ts')).toBe(false); + }); + + it('returns false when access is denied', () => { + jest.spyOn(fs, 'accessSync').mockImplementation(() => { + throw new Error('EACCES'); + }); + + expect(fileExists('/protected/file.ts')).toBe(false); + }); +}); diff --git a/src/__tests__/helpers/fs/folderExists.test.ts b/src/__tests__/helpers/fs/folderExists.test.ts new file mode 100644 index 0000000..1b80aa6 --- /dev/null +++ b/src/__tests__/helpers/fs/folderExists.test.ts @@ -0,0 +1,30 @@ +import * as fs from 'node:fs'; +import { folderExists } from '../../../helpers/fs/folderExists'; + +describe('folderExists', () => { + afterEach(() => jest.restoreAllMocks()); + + it('returns true when path is a directory', () => { + jest + .spyOn(fs, 'statSync') + .mockReturnValue({ isDirectory: () => true } as fs.Stats); + + expect(folderExists('/project/src')).toBe(true); + }); + + it('returns false when path is a file, not a directory', () => { + jest + .spyOn(fs, 'statSync') + .mockReturnValue({ isDirectory: () => false } as fs.Stats); + + expect(folderExists('/project/file.ts')).toBe(false); + }); + + it('returns false when path does not exist', () => { + jest.spyOn(fs, 'statSync').mockImplementation(() => { + throw new Error('ENOENT'); + }); + + expect(folderExists('/missing/dir')).toBe(false); + }); +}); diff --git a/src/__tests__/helpers/fs/listFiles.test.ts b/src/__tests__/helpers/fs/listFiles.test.ts new file mode 100644 index 0000000..0cf38f8 --- /dev/null +++ b/src/__tests__/helpers/fs/listFiles.test.ts @@ -0,0 +1,56 @@ +import * as fs from 'node:fs'; +import { listFiles } from '../../../helpers/fs/listFiles'; + +describe('listFiles', () => { + afterEach(() => jest.restoreAllMocks()); + + it('returns array of filenames on success', () => { + jest + .spyOn(fs, 'readdirSync') + .mockReturnValue(['Button.tsx', 'index.ts', 'Button.test.tsx'] as never); + + const result = listFiles('/project/src/Button'); + + expect(result).toEqual({ + success: true, + value: ['Button.tsx', 'index.ts', 'Button.test.tsx'], + }); + expect(fs.readdirSync).toHaveBeenCalledWith('/project/src/Button'); + }); + + it('returns empty array for empty directory', () => { + jest.spyOn(fs, 'readdirSync').mockReturnValue([] as never); + + const result = listFiles('/project/empty'); + + expect(result).toEqual({ success: true, value: [] }); + }); + + it('returns FOLDER_LIST_ERROR when directory does not exist', () => { + jest.spyOn(fs, 'readdirSync').mockImplementation(() => { + throw new Error('ENOENT: no such file or directory'); + }); + + const result = listFiles('/missing/dir'); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.code).toBe('FOLDER_LIST_ERROR'); + expect(result.error.message).toContain('/missing/dir'); + } + }); + + it('preserves the original error as cause', () => { + const originalError = new Error('ENOENT'); + jest.spyOn(fs, 'readdirSync').mockImplementation(() => { + throw originalError; + }); + + const result = listFiles('/dir'); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.cause).toBe(originalError); + } + }); +}); diff --git a/src/__tests__/helpers/fs/readFile.test.ts b/src/__tests__/helpers/fs/readFile.test.ts new file mode 100644 index 0000000..f0a5f28 --- /dev/null +++ b/src/__tests__/helpers/fs/readFile.test.ts @@ -0,0 +1,43 @@ +import * as fs from 'node:fs'; +import { readFile } from '../../../helpers/fs/readFile'; + +describe('readFile', () => { + afterEach(() => jest.restoreAllMocks()); + + it('returns file content on success', () => { + jest.spyOn(fs, 'readFileSync').mockReturnValue('file content' as never); + + const result = readFile('/project/file.ts'); + + expect(result).toEqual({ success: true, value: 'file content' }); + expect(fs.readFileSync).toHaveBeenCalledWith('/project/file.ts', 'utf-8'); + }); + + it('returns FILE_READ_ERROR when file does not exist', () => { + jest.spyOn(fs, 'readFileSync').mockImplementation(() => { + throw new Error('ENOENT: no such file or directory'); + }); + + const result = readFile('/missing/file.ts'); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.code).toBe('FILE_READ_ERROR'); + expect(result.error.message).toContain('/missing/file.ts'); + } + }); + + it('preserves the original error as cause', () => { + const originalError = new Error('ENOENT'); + jest.spyOn(fs, 'readFileSync').mockImplementation(() => { + throw originalError; + }); + + const result = readFile('/file.ts'); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.cause).toBe(originalError); + } + }); +}); diff --git a/src/__tests__/helpers/template/compileTemplate.test.ts b/src/__tests__/helpers/template/compileTemplate.test.ts new file mode 100644 index 0000000..9733a7a --- /dev/null +++ b/src/__tests__/helpers/template/compileTemplate.test.ts @@ -0,0 +1,65 @@ +import { compileTemplate } from '../../../helpers/template/compileTemplate'; + +describe('compileTemplate', () => { + it('replaces template variables with provided data', () => { + const result = compileTemplate('export default function {{name}}() {}', { + name: 'Button', + }); + + expect(result).toEqual({ + success: true, + value: 'export default function Button() {}', + }); + }); + + it('replaces multiple occurrences of the same variable', () => { + const template = + 'interface {{name}}Props {}\nexport const {{name}} = () => null'; + const result = compileTemplate(template, { name: 'Card' }); + + expect(result).toEqual({ + success: true, + value: 'interface CardProps {}\nexport const Card = () => null', + }); + }); + + it('supports multiple different variables', () => { + const result = compileTemplate('{{name}} extends {{base}}', { + name: 'Child', + base: 'Parent', + }); + + expect(result).toEqual({ success: true, value: 'Child extends Parent' }); + }); + + it('leaves template unchanged when data is empty object', () => { + const result = compileTemplate('no variables here', {}); + + expect(result).toEqual({ success: true, value: 'no variables here' }); + }); + + it('compiles empty template string', () => { + const result = compileTemplate('', {}); + + expect(result).toEqual({ success: true, value: '' }); + }); + + it('returns TEMPLATE_COMPILE_ERROR for invalid Handlebars syntax', () => { + const result = compileTemplate('{{#if}}broken{{/each}}', {}); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.code).toBe('TEMPLATE_COMPILE_ERROR'); + } + }); + + it('preserves non-variable content', () => { + const template = `import React from 'react'\n\nconst {{name}} = () =>
`; + const result = compileTemplate(template, { name: 'MyComp' }); + + expect(result).toEqual({ + success: true, + value: `import React from 'react'\n\nconst MyComp = () =>
`, + }); + }); +}); diff --git a/src/__tests__/helpers/template/isTemplateFile.test.ts b/src/__tests__/helpers/template/isTemplateFile.test.ts new file mode 100644 index 0000000..950a4c1 --- /dev/null +++ b/src/__tests__/helpers/template/isTemplateFile.test.ts @@ -0,0 +1,22 @@ +import { isTemplateFile } from '../../../helpers/template/isTemplateFile'; + +describe('isTemplateFile', () => { + it('returns true for .hbs files', () => { + expect(isTemplateFile('Button.tsx.hbs')).toBe(true); + expect(isTemplateFile('index.ts.hbs')).toBe(true); + expect(isTemplateFile('styles.css.hbs')).toBe(true); + expect(isTemplateFile('.hbs')).toBe(true); + }); + + it('returns false for non-.hbs files', () => { + expect(isTemplateFile('Button.tsx')).toBe(false); + expect(isTemplateFile('index.ts')).toBe(false); + expect(isTemplateFile('Button.hbs.tsx')).toBe(false); + expect(isTemplateFile('')).toBe(false); + }); + + it('is case-sensitive (.HBS is not a template file)', () => { + expect(isTemplateFile('Button.HBS')).toBe(false); + expect(isTemplateFile('index.Hbs')).toBe(false); + }); +}); diff --git a/src/__tests__/helpers/template/listTemplates.test.ts b/src/__tests__/helpers/template/listTemplates.test.ts new file mode 100644 index 0000000..74d3a80 --- /dev/null +++ b/src/__tests__/helpers/template/listTemplates.test.ts @@ -0,0 +1,52 @@ +import * as vscode from 'vscode'; +import { listTemplates } from '../../../helpers/template/listTemplates'; + +const mockFs = vscode.workspace.fs as jest.Mocked; + +describe('listTemplates', () => { + beforeEach(() => jest.clearAllMocks()); + + const uri = vscode.Uri.file('/workspace/templates'); + + it('returns only directory names', async () => { + mockFs.readDirectory.mockResolvedValue([ + ['ComponentSass', 2], + ['ComponentTailwind', 2], + ['README.md', 1], + ] as never); + + const result = await listTemplates(uri); + + expect(result).toEqual({ + success: true, + value: ['ComponentSass', 'ComponentTailwind'], + }); + }); + + it('returns empty array when no directories exist', async () => { + mockFs.readDirectory.mockResolvedValue([['README.md', 1]] as never); + + const result = await listTemplates(uri); + + expect(result).toEqual({ success: true, value: [] }); + }); + + it('returns empty array for empty templates folder', async () => { + mockFs.readDirectory.mockResolvedValue([]); + + const result = await listTemplates(uri); + + expect(result).toEqual({ success: true, value: [] }); + }); + + it('propagates error when folder cannot be read', async () => { + mockFs.readDirectory.mockRejectedValue(new Error('ENOENT')); + + const result = await listTemplates(uri); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.code).toBe('WORKSPACE_DIR_LIST_ERROR'); + } + }); +}); diff --git a/src/__tests__/helpers/template/processTemplateFolder.test.ts b/src/__tests__/helpers/template/processTemplateFolder.test.ts new file mode 100644 index 0000000..8e3c3db --- /dev/null +++ b/src/__tests__/helpers/template/processTemplateFolder.test.ts @@ -0,0 +1,178 @@ +import * as vscode from 'vscode'; +import { processTemplateFolder } from '../../../helpers/template/processTemplateFolder'; + +const mockFs = vscode.workspace.fs as jest.Mocked; + +const encode = (s: string) => new TextEncoder().encode(s); + +describe('processTemplateFolder', () => { + beforeEach(() => jest.clearAllMocks()); + + const templateUri = vscode.Uri.file('/workspace/templates/ComponentSass'); + + describe('ComponentSass template', () => { + beforeEach(() => { + mockFs.readDirectory.mockResolvedValue([ + ['ComponentSass.tsx.hbs', 1], + ['ComponentSass.module.scss.hbs', 1], + ['index.tsx.hbs', 1], + ] as never); + + mockFs.readFile + .mockResolvedValueOnce( + encode('export const {{name}} = () =>
;') as never, + ) + .mockResolvedValueOnce(encode('.{{name}} { color: red; }') as never) + .mockResolvedValueOnce( + encode("export { default } from './{{name}}';") as never, + ); + }); + + it('renames template files and compiles content', async () => { + const result = await processTemplateFolder( + templateUri, + 'ComponentSass', + 'Button', + ); + + expect(result.success).toBe(true); + if (result.success) { + const paths = result.value.map(f => f.targetRelativePath); + expect(paths).toContain('Button.tsx'); + expect(paths).toContain('Button.module.scss'); + expect(paths).toContain('index.tsx'); + } + }); + + it('compiles Handlebars expressions with component name', async () => { + const result = await processTemplateFolder( + templateUri, + 'ComponentSass', + 'Button', + ); + + expect(result.success).toBe(true); + if (result.success) { + const main = result.value.find( + f => f.targetRelativePath === 'Button.tsx', + ); + expect(main?.content).toBe('export const Button = () =>
;'); + + const scss = result.value.find( + f => f.targetRelativePath === 'Button.module.scss', + ); + expect(scss?.content).toBe('.Button { color: red; }'); + } + }); + }); + + describe('ReactPackage template with nested directories', () => { + it('preserves nested directory structure in target paths', async () => { + mockFs.readDirectory + .mockResolvedValueOnce([ + ['src', 2], + ['package.json.hbs', 1], + ] as never) + .mockResolvedValueOnce([ + ['App.tsx.hbs', 1], + ['lib', 2], + ] as never) + .mockResolvedValueOnce([['main.ts.hbs', 1]] as never); + + mockFs.readFile.mockResolvedValue(encode('// {{name}}') as never); + + const result = await processTemplateFolder( + templateUri, + 'ReactPackage', + 'MyApp', + ); + + expect(result.success).toBe(true); + if (result.success) { + const paths = result.value.map(f => f.targetRelativePath); + expect(paths).toContain('src/App.tsx'); + expect(paths).toContain('src/lib/main.ts'); + expect(paths).toContain('package.json'); + } + }); + }); + + describe('error handling', () => { + it('propagates walk error', async () => { + mockFs.readDirectory.mockRejectedValue(new Error('ENOENT')); + + const result = await processTemplateFolder( + templateUri, + 'ComponentSass', + 'Button', + ); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.code).toBe('WORKSPACE_DIR_LIST_ERROR'); + } + }); + + it('propagates read error for a template file', async () => { + mockFs.readDirectory.mockResolvedValue([ + ['ComponentSass.tsx.hbs', 1], + ] as never); + mockFs.readFile.mockRejectedValue(new Error('Read failed')); + + const result = await processTemplateFolder( + templateUri, + 'ComponentSass', + 'Button', + ); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.code).toBe('WORKSPACE_FILE_READ_ERROR'); + } + }); + + it('returns error when two template files compile to the same output path', async () => { + // Both "ComponentSass.tsx.hbs" and "Template.tsx.hbs" resolve to "Button.tsx" + mockFs.readDirectory.mockResolvedValue([ + ['ComponentSass.tsx.hbs', 1], + ['Template.tsx.hbs', 1], + ] as never); + + mockFs.readFile.mockResolvedValue(encode('// {{name}}') as never); + + const result = await processTemplateFolder( + templateUri, + 'ComponentSass', + 'Button', + ); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.code).toBe('TEMPLATE_DUPLICATE_PATH'); + } + }); + }); + + describe('non-.hbs files', () => { + it('skips files that are not .hbs templates', async () => { + mockFs.readDirectory.mockResolvedValue([ + ['Button.tsx.hbs', 1], + ['.gitkeep', 1], + ] as never); + + mockFs.readFile.mockResolvedValue(encode('// {{name}}') as never); + + const result = await processTemplateFolder( + templateUri, + 'ComponentSass', + 'Button', + ); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.value).toHaveLength(1); + expect(result.value[0].targetRelativePath).toBe('Button.tsx'); + } + }); + }); +}); diff --git a/src/__tests__/helpers/template/removeTemplateExtension.test.ts b/src/__tests__/helpers/template/removeTemplateExtension.test.ts new file mode 100644 index 0000000..caac5d5 --- /dev/null +++ b/src/__tests__/helpers/template/removeTemplateExtension.test.ts @@ -0,0 +1,26 @@ +import { removeTemplateExtension } from '../../../helpers/template/removeTemplateExtension'; + +describe('removeTemplateExtension', () => { + it('removes .hbs extension from template file names', () => { + expect(removeTemplateExtension('Button.tsx.hbs')).toBe('Button.tsx'); + expect(removeTemplateExtension('index.ts.hbs')).toBe('index.ts'); + expect(removeTemplateExtension('styles.css.hbs')).toBe('styles.css'); + }); + + it('removes only the trailing .hbs extension', () => { + expect(removeTemplateExtension('.hbs')).toBe(''); + }); + + it('returns the filename unchanged when it has no .hbs extension', () => { + expect(removeTemplateExtension('Button.tsx')).toBe('Button.tsx'); + expect(removeTemplateExtension('index.ts')).toBe('index.ts'); + expect(removeTemplateExtension('')).toBe(''); + }); + + it('does not remove .hbs when it is not at the end', () => { + expect(removeTemplateExtension('Button.hbs.tsx')).toBe('Button.hbs.tsx'); + expect(removeTemplateExtension('my.hbs.component.ts')).toBe( + 'my.hbs.component.ts', + ); + }); +}); diff --git a/src/__tests__/helpers/template/renameTemplateFile.test.ts b/src/__tests__/helpers/template/renameTemplateFile.test.ts new file mode 100644 index 0000000..4a6a3d6 --- /dev/null +++ b/src/__tests__/helpers/template/renameTemplateFile.test.ts @@ -0,0 +1,86 @@ +import { renameTemplateFile } from '../../../helpers/template/renameTemplateFile'; + +describe('renameTemplateFile', () => { + describe('replacing template folder name', () => { + it('replaces template name at the start of the filename', () => { + expect( + renameTemplateFile('ComponentSass.tsx', 'ComponentSass', 'Button'), + ).toBe('Button.tsx'); + }); + + it('replaces template name preserving compound extensions', () => { + expect( + renameTemplateFile( + 'ComponentSass.module.scss', + 'ComponentSass', + 'Button', + ), + ).toBe('Button.module.scss'); + expect( + renameTemplateFile('ComponentSass.spec.tsx', 'ComponentSass', 'Button'), + ).toBe('Button.spec.tsx'); + }); + + it('replaces template name in the middle of the filename', () => { + expect( + renameTemplateFile('StoriesTsx.stories.tsx', 'StoriesTsx', 'Button'), + ).toBe('Button.stories.tsx'); + }); + + it('replaces ComponentTailwind with double extension style', () => { + expect( + renameTemplateFile( + 'ComponentTailwind.style.ts', + 'ComponentTailwind', + 'Card', + ), + ).toBe('Card.style.ts'); + }); + }); + + describe('replacing "Template" literal fallback', () => { + it('replaces "Template" when template name is not found', () => { + expect( + renameTemplateFile('Template.tsx', 'ComponentSass', 'Button'), + ).toBe('Button.tsx'); + }); + + it('replaces "Template" in compound filenames', () => { + expect( + renameTemplateFile('Template.stories.tsx', 'MyTemplate', 'Card'), + ).toBe('Card.stories.tsx'); + }); + }); + + describe('files that should not be renamed', () => { + it('returns index.tsx unchanged', () => { + expect(renameTemplateFile('index.tsx', 'ComponentSass', 'Button')).toBe( + 'index.tsx', + ); + }); + + it('returns styles.ts unchanged (ComponentStyled)', () => { + expect(renameTemplateFile('styles.ts', 'ComponentSyled', 'Button')).toBe( + 'styles.ts', + ); + }); + + it('returns static config files unchanged', () => { + expect(renameTemplateFile('package.json', 'ReactPackage', 'MyApp')).toBe( + 'package.json', + ); + expect(renameTemplateFile('tsconfig.json', 'ReactPackage', 'MyApp')).toBe( + 'tsconfig.json', + ); + expect( + renameTemplateFile('vite.config.ts', 'ReactPackage', 'MyApp'), + ).toBe('vite.config.ts'); + }); + + it('returns index.html unchanged', () => { + expect(renameTemplateFile('index.html', 'ReactPackage', 'MyApp')).toBe( + 'index.html', + ); + }); + }); +}); diff --git a/src/__tests__/helpers/template/resolveTargetPath.test.ts b/src/__tests__/helpers/template/resolveTargetPath.test.ts new file mode 100644 index 0000000..aa854b0 --- /dev/null +++ b/src/__tests__/helpers/template/resolveTargetPath.test.ts @@ -0,0 +1,109 @@ +import { resolveTargetPath } from '../../../helpers/template/resolveTargetPath'; + +describe('resolveTargetPath', () => { + describe('ComponentSass template (4 files)', () => { + const T = 'ComponentSass'; + const C = 'Button'; + + it('renames ComponentSass.tsx → Button.tsx', () => { + expect(resolveTargetPath('ComponentSass.tsx', T, C)).toBe('Button.tsx'); + }); + + it('renames ComponentSass.module.scss → Button.module.scss', () => { + expect(resolveTargetPath('ComponentSass.module.scss', T, C)).toBe( + 'Button.module.scss', + ); + }); + + it('renames ComponentSass.spec.tsx → Button.spec.tsx', () => { + expect(resolveTargetPath('ComponentSass.spec.tsx', T, C)).toBe( + 'Button.spec.tsx', + ); + }); + + it('leaves index.tsx unchanged', () => { + expect(resolveTargetPath('index.tsx', T, C)).toBe('index.tsx'); + }); + }); + + describe('ComponentTailwind template (3 files)', () => { + const T = 'ComponentTailwind'; + const C = 'Card'; + + it('renames ComponentTailwind.tsx → Card.tsx', () => { + expect(resolveTargetPath('ComponentTailwind.tsx', T, C)).toBe('Card.tsx'); + }); + + it('renames ComponentTailwind.style.ts → Card.style.ts', () => { + expect(resolveTargetPath('ComponentTailwind.style.ts', T, C)).toBe( + 'Card.style.ts', + ); + }); + + it('leaves index.tsx unchanged', () => { + expect(resolveTargetPath('index.tsx', T, C)).toBe('index.tsx'); + }); + }); + + describe('StoriesTsx template (1 file)', () => { + it('renames StoriesTsx.stories.tsx → Button.stories.tsx', () => { + expect( + resolveTargetPath('StoriesTsx.stories.tsx', 'StoriesTsx', 'Button'), + ).toBe('Button.stories.tsx'); + }); + }); + + describe('ReactPackage template (nested dirs)', () => { + const T = 'ReactPackage'; + const C = 'MyPackage'; + + it('preserves root-level static files unchanged', () => { + expect(resolveTargetPath('package.json', T, C)).toBe('package.json'); + expect(resolveTargetPath('index.html', T, C)).toBe('index.html'); + expect(resolveTargetPath('vite.config.ts', T, C)).toBe('vite.config.ts'); + }); + + it('preserves nested path structure for src/ files', () => { + expect(resolveTargetPath('src/App.tsx', T, C)).toBe('src/App.tsx'); + expect(resolveTargetPath('src/main.tsx', T, C)).toBe('src/main.tsx'); + }); + + it('preserves deeply nested path structure for src/lib/', () => { + expect(resolveTargetPath('src/lib/main.tsx', T, C)).toBe( + 'src/lib/main.tsx', + ); + }); + }); + + describe('path integrity', () => { + it('only renames the filename, never the directory segments', () => { + expect( + resolveTargetPath( + 'ComponentSass/nested/ComponentSass.tsx', + 'ComponentSass', + 'Button', + ), + ).toBe('ComponentSass/nested/Button.tsx'); + }); + }); + + describe('path traversal safety', () => { + it('strips .. segments, preserving remaining path', () => { + expect( + resolveTargetPath('../../etc/passwd', 'ComponentSass', 'Button'), + ).toBe('etc/passwd'); + }); + + it('strips . segments', () => { + expect(resolveTargetPath('./index.tsx', 'ComponentSass', 'Button')).toBe( + 'index.tsx', + ); + }); + + it('strips leading empty segments from absolute paths', () => { + expect( + resolveTargetPath('/absolute/path/file.tsx', 'ComponentSass', 'Button'), + ).toBe('absolute/path/file.tsx'); + }); + }); +}); diff --git a/src/__tests__/helpers/template/walkTemplateFolder.test.ts b/src/__tests__/helpers/template/walkTemplateFolder.test.ts new file mode 100644 index 0000000..48ed6e8 --- /dev/null +++ b/src/__tests__/helpers/template/walkTemplateFolder.test.ts @@ -0,0 +1,98 @@ +import * as vscode from 'vscode'; +import { walkTemplateFolder } from '../../../helpers/template/walkTemplateFolder'; + +const mockFs = vscode.workspace.fs as jest.Mocked; + +describe('walkTemplateFolder', () => { + beforeEach(() => jest.clearAllMocks()); + + const templateUri = vscode.Uri.file('/workspace/templates/ComponentSass'); + + it('returns flat list for a flat template folder', async () => { + mockFs.readDirectory.mockResolvedValue([ + ['ComponentSass.tsx.hbs', 1], + ['ComponentSass.module.scss.hbs', 1], + ['index.tsx.hbs', 1], + ] as never); + + const result = await walkTemplateFolder(templateUri); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.value.map(e => e.relativePath)).toEqual([ + 'ComponentSass.tsx.hbs', + 'ComponentSass.module.scss.hbs', + 'index.tsx.hbs', + ]); + } + }); + + it('returns nested paths for a template with subdirectories', async () => { + mockFs.readDirectory + .mockResolvedValueOnce([ + ['src', 2], + ['package.json.hbs', 1], + ] as never) + .mockResolvedValueOnce([ + ['main.tsx.hbs', 1], + ['lib', 2], + ] as never) + .mockResolvedValueOnce([['index.ts.hbs', 1]] as never); + + const result = await walkTemplateFolder(templateUri); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.value.map(e => e.relativePath)).toEqual([ + 'src/main.tsx.hbs', + 'src/lib/index.ts.hbs', + 'package.json.hbs', + ]); + } + }); + + it('returns empty array for an empty template folder', async () => { + mockFs.readDirectory.mockResolvedValue([]); + + const result = await walkTemplateFolder(templateUri); + + expect(result).toEqual({ success: true, value: [] }); + }); + + it('propagates error from nested directory read', async () => { + mockFs.readDirectory + .mockResolvedValueOnce([['src', 2]] as never) + .mockRejectedValueOnce(new Error('Permission denied')); + + const result = await walkTemplateFolder(templateUri); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.code).toBe('WORKSPACE_DIR_LIST_ERROR'); + } + }); + + it('includes the uri for each entry', async () => { + mockFs.readDirectory.mockResolvedValue([['Button.tsx.hbs', 1]] as never); + + const result = await walkTemplateFolder(templateUri); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.value[0].uri).toBeDefined(); + expect(result.value[0].uri.fsPath).toContain('Button.tsx.hbs'); + } + }); + + it('returns error when folder nesting exceeds MAX_DEPTH', async () => { + // Simulate 11 levels of nesting (depth 0–10 = 11 calls, depth 11 = exceeded) + mockFs.readDirectory.mockResolvedValue([['nested', 2]] as never); + + const result = await walkTemplateFolder(templateUri); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.code).toBe('TEMPLATE_MAX_DEPTH_EXCEEDED'); + } + }); +}); diff --git a/src/__tests__/helpers/vscode/getWorkspaceFolders.test.ts b/src/__tests__/helpers/vscode/getWorkspaceFolders.test.ts new file mode 100644 index 0000000..3810b8b --- /dev/null +++ b/src/__tests__/helpers/vscode/getWorkspaceFolders.test.ts @@ -0,0 +1,59 @@ +import * as vscode from 'vscode'; +import { getWorkspaceFolders } from '../../../helpers/vscode/getWorkspaceFolders'; + +describe('getWorkspaceFolders', () => { + const originalFolders = vscode.workspace.workspaceFolders; + + afterEach(() => { + Object.defineProperty(vscode.workspace, 'workspaceFolders', { + value: originalFolders, + writable: true, + }); + }); + + it('returns mapped workspace folders when folders exist', () => { + Object.defineProperty(vscode.workspace, 'workspaceFolders', { + value: [ + { uri: { fsPath: '/project/root' }, name: 'root', index: 0 }, + { uri: { fsPath: '/project/packages/app' }, name: 'app', index: 1 }, + ], + writable: true, + }); + + const result = getWorkspaceFolders(); + + expect(result).toEqual([ + { name: 'root', path: '/project/root', index: 0 }, + { name: 'app', path: '/project/packages/app', index: 1 }, + ]); + }); + + it('returns undefined when workspaceFolders is undefined', () => { + Object.defineProperty(vscode.workspace, 'workspaceFolders', { + value: undefined, + writable: true, + }); + + expect(getWorkspaceFolders()).toBeUndefined(); + }); + + it('returns undefined when workspaceFolders is empty array', () => { + Object.defineProperty(vscode.workspace, 'workspaceFolders', { + value: [], + writable: true, + }); + + expect(getWorkspaceFolders()).toBeUndefined(); + }); + + it('maps fsPath to path field', () => { + Object.defineProperty(vscode.workspace, 'workspaceFolders', { + value: [{ uri: { fsPath: '/my/project' }, name: 'project', index: 0 }], + writable: true, + }); + + const result = getWorkspaceFolders(); + + expect(result?.[0].path).toBe('/my/project'); + }); +}); diff --git a/src/__tests__/helpers/vscode/promptInput.test.ts b/src/__tests__/helpers/vscode/promptInput.test.ts new file mode 100644 index 0000000..0f0f6bc --- /dev/null +++ b/src/__tests__/helpers/vscode/promptInput.test.ts @@ -0,0 +1,58 @@ +import * as vscode from 'vscode'; +import { promptInput } from '../../../helpers/vscode/promptInput'; + +const mockShowInputBox = vscode.window.showInputBox as jest.Mock; + +describe('promptInput', () => { + beforeEach(() => jest.clearAllMocks()); + + it('calls showInputBox with mapped options', async () => { + mockShowInputBox.mockResolvedValue('MyComponent'); + + const result = await promptInput({ + prompt: 'Enter component name', + placeholder: 'e.g. Button', + value: 'Default', + }); + + expect(result).toBe('MyComponent'); + expect(mockShowInputBox).toHaveBeenCalledWith({ + prompt: 'Enter component name', + placeHolder: 'e.g. Button', + value: 'Default', + validateInput: undefined, + }); + }); + + it('calls showInputBox with no options when called without arguments', async () => { + mockShowInputBox.mockResolvedValue('SomeValue'); + + await promptInput(); + + expect(mockShowInputBox).toHaveBeenCalledWith({ + prompt: undefined, + placeHolder: undefined, + value: undefined, + validateInput: undefined, + }); + }); + + it('returns undefined when user cancels the input box', async () => { + mockShowInputBox.mockResolvedValue(undefined); + + const result = await promptInput({ prompt: 'Enter name' }); + + expect(result).toBeUndefined(); + }); + + it('passes validateInput function through to vscode', async () => { + mockShowInputBox.mockResolvedValue('valid'); + const validate = (v: string) => (v.length < 3 ? 'Too short' : undefined); + + await promptInput({ validateInput: validate }); + + expect(mockShowInputBox).toHaveBeenCalledWith( + expect.objectContaining({ validateInput: validate }), + ); + }); +}); diff --git a/src/__tests__/helpers/vscode/promptSelection.test.ts b/src/__tests__/helpers/vscode/promptSelection.test.ts new file mode 100644 index 0000000..caf584e --- /dev/null +++ b/src/__tests__/helpers/vscode/promptSelection.test.ts @@ -0,0 +1,55 @@ +import * as vscode from 'vscode'; +import { promptSelection } from '../../../helpers/vscode/promptSelection'; + +const mockShowQuickPick = vscode.window.showQuickPick as jest.Mock; + +describe('promptSelection', () => { + beforeEach(() => jest.clearAllMocks()); + + it('calls showQuickPick with items and placeholder', async () => { + mockShowQuickPick.mockResolvedValue('ComponentA'); + + const result = await promptSelection(['ComponentA', 'ComponentB'], { + placeholder: 'Select a template', + }); + + expect(result).toBe('ComponentA'); + expect(mockShowQuickPick).toHaveBeenCalledWith( + ['ComponentA', 'ComponentB'], + { + placeHolder: 'Select a template', + canPickMany: false, + }, + ); + }); + + it('calls showQuickPick with default options when none provided', async () => { + mockShowQuickPick.mockResolvedValue('Item'); + + await promptSelection(['Item']); + + expect(mockShowQuickPick).toHaveBeenCalledWith(['Item'], { + placeHolder: undefined, + canPickMany: false, + }); + }); + + it('returns undefined when user dismisses the picker', async () => { + mockShowQuickPick.mockResolvedValue(undefined); + + const result = await promptSelection(['A', 'B']); + + expect(result).toBeUndefined(); + }); + + it('does not mutate the original items array', async () => { + mockShowQuickPick.mockResolvedValue('A'); + const items = ['A', 'B'] as const; + + await promptSelection(items); + + const passedItems = mockShowQuickPick.mock.calls[0][0]; + expect(passedItems).not.toBe(items); + expect(passedItems).toEqual(['A', 'B']); + }); +}); diff --git a/src/__tests__/helpers/vscode/showError.test.ts b/src/__tests__/helpers/vscode/showError.test.ts new file mode 100644 index 0000000..d054745 --- /dev/null +++ b/src/__tests__/helpers/vscode/showError.test.ts @@ -0,0 +1,21 @@ +import * as vscode from 'vscode'; +import { showError } from '../../../helpers/vscode/showError'; + +const mockShowError = vscode.window.showErrorMessage as jest.Mock; + +describe('showError', () => { + beforeEach(() => jest.clearAllMocks()); + + it('calls showErrorMessage with the given message', () => { + showError('Template not found'); + + expect(mockShowError).toHaveBeenCalledWith('Template not found'); + expect(mockShowError).toHaveBeenCalledTimes(1); + }); + + it('calls showErrorMessage with empty string', () => { + showError(''); + + expect(mockShowError).toHaveBeenCalledWith(''); + }); +}); diff --git a/src/__tests__/helpers/vscode/showInfo.test.ts b/src/__tests__/helpers/vscode/showInfo.test.ts new file mode 100644 index 0000000..6af9d42 --- /dev/null +++ b/src/__tests__/helpers/vscode/showInfo.test.ts @@ -0,0 +1,21 @@ +import * as vscode from 'vscode'; +import { showInfo } from '../../../helpers/vscode/showInfo'; + +const mockShowInfo = vscode.window.showInformationMessage as jest.Mock; + +describe('showInfo', () => { + beforeEach(() => jest.clearAllMocks()); + + it('calls showInformationMessage with the given message', () => { + showInfo('Component created successfully'); + + expect(mockShowInfo).toHaveBeenCalledWith('Component created successfully'); + expect(mockShowInfo).toHaveBeenCalledTimes(1); + }); + + it('calls showInformationMessage with empty string', () => { + showInfo(''); + + expect(mockShowInfo).toHaveBeenCalledWith(''); + }); +}); diff --git a/src/__tests__/helpers/vscode/workspace/createFolder.test.ts b/src/__tests__/helpers/vscode/workspace/createFolder.test.ts new file mode 100644 index 0000000..0290080 --- /dev/null +++ b/src/__tests__/helpers/vscode/workspace/createFolder.test.ts @@ -0,0 +1,43 @@ +import * as vscode from 'vscode'; +import { createWorkspaceFolder } from '../../../../helpers/vscode/workspace/createFolder'; + +const mockFs = vscode.workspace.fs as jest.Mocked; + +describe('createWorkspaceFolder', () => { + beforeEach(() => jest.clearAllMocks()); + + const uri = vscode.Uri.file('/workspace/src/Button'); + + it('calls createDirectory with the given URI', async () => { + mockFs.createDirectory.mockResolvedValue(undefined); + + const result = await createWorkspaceFolder(uri); + + expect(result).toEqual({ success: true, value: undefined }); + expect(mockFs.createDirectory).toHaveBeenCalledWith(uri); + }); + + it('returns WORKSPACE_FOLDER_CREATE_ERROR when creation fails', async () => { + mockFs.createDirectory.mockRejectedValue(new Error('NoPermissions')); + + const result = await createWorkspaceFolder(uri); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.code).toBe('WORKSPACE_FOLDER_CREATE_ERROR'); + expect(result.error.message).toContain(uri.fsPath); + } + }); + + it('preserves the original error as cause', async () => { + const originalError = new Error('NoPermissions'); + mockFs.createDirectory.mockRejectedValue(originalError); + + const result = await createWorkspaceFolder(uri); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.cause).toBe(originalError); + } + }); +}); diff --git a/src/__tests__/helpers/vscode/workspace/fileExists.test.ts b/src/__tests__/helpers/vscode/workspace/fileExists.test.ts new file mode 100644 index 0000000..36714a7 --- /dev/null +++ b/src/__tests__/helpers/vscode/workspace/fileExists.test.ts @@ -0,0 +1,29 @@ +import * as vscode from 'vscode'; +import { workspaceFileExists } from '../../../../helpers/vscode/workspace/fileExists'; + +const mockFs = vscode.workspace.fs as jest.Mocked; + +describe('workspaceFileExists', () => { + beforeEach(() => jest.clearAllMocks()); + + const uri = vscode.Uri.file('/workspace/src/Button/Button.tsx'); + + it('returns true when stat succeeds', async () => { + mockFs.stat.mockResolvedValue({ type: 1, ctime: 0, mtime: 0, size: 100 }); + + expect(await workspaceFileExists(uri)).toBe(true); + expect(mockFs.stat).toHaveBeenCalledWith(uri); + }); + + it('returns false when stat throws (file not found)', async () => { + mockFs.stat.mockRejectedValue(new Error('FileNotFound')); + + expect(await workspaceFileExists(uri)).toBe(false); + }); + + it('returns false for any stat error (permission denied, etc.)', async () => { + mockFs.stat.mockRejectedValue(new Error('NoPermissions')); + + expect(await workspaceFileExists(uri)).toBe(false); + }); +}); diff --git a/src/__tests__/helpers/vscode/workspace/listEntries.test.ts b/src/__tests__/helpers/vscode/workspace/listEntries.test.ts new file mode 100644 index 0000000..fff8c03 --- /dev/null +++ b/src/__tests__/helpers/vscode/workspace/listEntries.test.ts @@ -0,0 +1,80 @@ +import * as vscode from 'vscode'; +import { listWorkspaceEntries } from '../../../../helpers/vscode/workspace/listEntries'; + +const mockFs = vscode.workspace.fs as jest.Mocked; + +describe('listWorkspaceEntries', () => { + beforeEach(() => jest.clearAllMocks()); + + const uri = vscode.Uri.file('/workspace/templates/ComponentSass'); + + it('returns mapped entries with correct types', async () => { + mockFs.readDirectory.mockResolvedValue([ + ['ComponentSass.tsx.hbs', 1], // File + ['ComponentSass.spec.tsx.hbs', 1], // File + ['index.tsx.hbs', 1], // File + ['__snapshots__', 2], // Directory + ] as never); + + const result = await listWorkspaceEntries(uri); + + expect(result).toEqual({ + success: true, + value: [ + { name: 'ComponentSass.tsx.hbs', type: 'file' }, + { name: 'ComponentSass.spec.tsx.hbs', type: 'file' }, + { name: 'index.tsx.hbs', type: 'file' }, + { name: '__snapshots__', type: 'directory' }, + ], + }); + }); + + it('maps unknown FileType values to "other"', async () => { + mockFs.readDirectory.mockResolvedValue([ + ['symlink', 64], // SymbolicLink + ['unknown', 0], // Unknown + ] as never); + + const result = await listWorkspaceEntries(uri); + + expect(result).toEqual({ + success: true, + value: [ + { name: 'symlink', type: 'other' }, + { name: 'unknown', type: 'other' }, + ], + }); + }); + + it('returns empty array for empty directory', async () => { + mockFs.readDirectory.mockResolvedValue([]); + + const result = await listWorkspaceEntries(uri); + + expect(result).toEqual({ success: true, value: [] }); + }); + + it('returns WORKSPACE_DIR_LIST_ERROR when readDirectory fails', async () => { + mockFs.readDirectory.mockRejectedValue(new Error('FileNotFound')); + + const result = await listWorkspaceEntries(uri); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.code).toBe('WORKSPACE_DIR_LIST_ERROR'); + expect(result.error.message).toContain(uri.fsPath); + } + }); + + it('preserves the original error as cause', async () => { + const originalError = new Error('FileNotFound'); + mockFs.readDirectory.mockRejectedValue(originalError); + + const result = await listWorkspaceEntries(uri); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.cause).toBe(originalError); + } + }); +}); diff --git a/src/__tests__/helpers/vscode/workspace/readFile.test.ts b/src/__tests__/helpers/vscode/workspace/readFile.test.ts new file mode 100644 index 0000000..77fecf9 --- /dev/null +++ b/src/__tests__/helpers/vscode/workspace/readFile.test.ts @@ -0,0 +1,53 @@ +import * as vscode from 'vscode'; +import { readWorkspaceFile } from '../../../../helpers/vscode/workspace/readFile'; + +const mockFs = vscode.workspace.fs as jest.Mocked; + +describe('readWorkspaceFile', () => { + beforeEach(() => jest.clearAllMocks()); + + const uri = vscode.Uri.file('/workspace/src/Button/Button.tsx'); + + it('returns decoded content on success', async () => { + const content = 'export default function Button() {}'; + mockFs.readFile.mockResolvedValue(new TextEncoder().encode(content)); + + const result = await readWorkspaceFile(uri); + + expect(result).toEqual({ success: true, value: content }); + expect(mockFs.readFile).toHaveBeenCalledWith(uri); + }); + + it('returns WORKSPACE_FILE_READ_ERROR when read fails', async () => { + mockFs.readFile.mockRejectedValue(new Error('FileNotFound')); + + const result = await readWorkspaceFile(uri); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.code).toBe('WORKSPACE_FILE_READ_ERROR'); + expect(result.error.message).toContain(uri.fsPath); + } + }); + + it('preserves the original error as cause', async () => { + const originalError = new Error('ENOENT'); + mockFs.readFile.mockRejectedValue(originalError); + + const result = await readWorkspaceFile(uri); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.cause).toBe(originalError); + } + }); + + it('decodes UTF-8 content correctly', async () => { + const content = 'const greeting = "Olá mundo 🌍"'; + mockFs.readFile.mockResolvedValue(new TextEncoder().encode(content)); + + const result = await readWorkspaceFile(uri); + + expect(result).toEqual({ success: true, value: content }); + }); +}); diff --git a/src/__tests__/helpers/vscode/workspace/writeFile.test.ts b/src/__tests__/helpers/vscode/workspace/writeFile.test.ts new file mode 100644 index 0000000..89239a0 --- /dev/null +++ b/src/__tests__/helpers/vscode/workspace/writeFile.test.ts @@ -0,0 +1,61 @@ +import * as vscode from 'vscode'; +import { writeWorkspaceFile } from '../../../../helpers/vscode/workspace/writeFile'; + +const mockFs = vscode.workspace.fs as jest.Mocked; + +describe('writeWorkspaceFile', () => { + beforeEach(() => jest.clearAllMocks()); + + const uri = vscode.Uri.file('/workspace/src/Button/Button.tsx'); + + it('writes encoded content to the workspace URI', async () => { + mockFs.writeFile.mockResolvedValue(undefined); + + const result = await writeWorkspaceFile( + uri, + 'export default function Button() {}', + ); + + expect(result).toEqual({ success: true, value: undefined }); + expect(mockFs.writeFile).toHaveBeenCalledWith( + uri, + new TextEncoder().encode('export default function Button() {}'), + ); + }); + + it('returns WORKSPACE_FILE_WRITE_ERROR when write fails', async () => { + mockFs.writeFile.mockRejectedValue(new Error('FileNotFound')); + + const result = await writeWorkspaceFile(uri, 'content'); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.code).toBe('WORKSPACE_FILE_WRITE_ERROR'); + expect(result.error.message).toContain(uri.fsPath); + } + }); + + it('preserves the original error as cause', async () => { + const originalError = new Error('EACCES'); + mockFs.writeFile.mockRejectedValue(originalError); + + const result = await writeWorkspaceFile(uri, ''); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.cause).toBe(originalError); + } + }); + + it('writes empty string content', async () => { + mockFs.writeFile.mockResolvedValue(undefined); + + const result = await writeWorkspaceFile(uri, ''); + + expect(result.success).toBe(true); + expect(mockFs.writeFile).toHaveBeenCalledWith( + uri, + new TextEncoder().encode(''), + ); + }); +}); diff --git a/src/__tests__/utils/formatToPascalCase.test.ts b/src/__tests__/utils/formatToPascalCase.test.ts new file mode 100644 index 0000000..c463954 --- /dev/null +++ b/src/__tests__/utils/formatToPascalCase.test.ts @@ -0,0 +1,72 @@ +import { formatToPascalCase } from '../../utils/formatToPascalCase'; + +describe('formatToPascalCase', () => { + describe('already PascalCase', () => { + it('leaves PascalCase unchanged', () => { + expect(formatToPascalCase('Button')).toBe('Button'); + expect(formatToPascalCase('MyComponent')).toBe('MyComponent'); + }); + }); + + describe('lowercase input', () => { + it('capitalizes a single word', () => { + expect(formatToPascalCase('button')).toBe('Button'); + expect(formatToPascalCase('card')).toBe('Card'); + }); + }); + + describe('kebab-case input', () => { + it('converts kebab-case to PascalCase', () => { + expect(formatToPascalCase('my-button')).toBe('MyButton'); + expect(formatToPascalCase('my-custom-card')).toBe('MyCustomCard'); + }); + }); + + describe('snake_case input', () => { + it('converts snake_case to PascalCase', () => { + expect(formatToPascalCase('my_button')).toBe('MyButton'); + expect(formatToPascalCase('MY_BUTTON')).toBe('MyButton'); + }); + }); + + describe('camelCase input', () => { + it('converts camelCase to PascalCase', () => { + expect(formatToPascalCase('myComponent')).toBe('MyComponent'); + expect(formatToPascalCase('myCustomCard')).toBe('MyCustomCard'); + }); + }); + + describe('mixed separators', () => { + it('handles spaces and mixed separators', () => { + expect(formatToPascalCase('my button')).toBe('MyButton'); + expect(formatToPascalCase('my-custom_card')).toBe('MyCustomCard'); + }); + }); + + describe('names with numbers', () => { + it('keeps trailing numbers attached to the word', () => { + expect(formatToPascalCase('button2')).toBe('Button2'); + expect(formatToPascalCase('h1-heading')).toBe('H1Heading'); + }); + }); + + describe('whitespace handling', () => { + it('trims leading and trailing whitespace', () => { + expect(formatToPascalCase(' button ')).toBe('Button'); + }); + }); + + describe('invalid input', () => { + it('returns empty string for empty input', () => { + expect(formatToPascalCase('')).toBe(''); + }); + + it('returns empty string for whitespace-only input', () => { + expect(formatToPascalCase(' ')).toBe(''); + }); + + it('preserves digit-only words without a leading letter', () => { + expect(formatToPascalCase('123')).toBe('123'); + }); + }); +}); diff --git a/src/__tests__/utils/validation.test.ts b/src/__tests__/utils/validation.test.ts new file mode 100644 index 0000000..d10c778 --- /dev/null +++ b/src/__tests__/utils/validation.test.ts @@ -0,0 +1,88 @@ +import { + COMPONENT_NAME_ERROR, + formatToPascalCase, + isValidComponentName, +} from '../../utils/validation'; + +describe('isValidComponentName', () => { + describe('validates already-formatted (PascalCase) names', () => { + it('accepts a PascalCase name', () => { + expect(isValidComponentName('Button')).toBe(true); + }); + + it('accepts a single uppercase letter', () => { + expect(isValidComponentName('A')).toBe(true); + }); + + it('accepts names with numbers after the first letter', () => { + expect(isValidComponentName('Button2')).toBe(true); + expect(isValidComponentName('H1Heading')).toBe(true); + }); + + it('accepts multi-word PascalCase', () => { + expect(isValidComponentName('MyComponent')).toBe(true); + }); + }); + + describe('rejects non-PascalCase or invalid names', () => { + it('rejects empty string', () => { + expect(isValidComponentName('')).toBe(false); + }); + + it('rejects names starting with a digit', () => { + expect(isValidComponentName('1Button')).toBe(false); + expect(isValidComponentName('1invalid')).toBe(false); + }); + + it('rejects lowercase names (use formatToPascalCase first)', () => { + expect(isValidComponentName('button')).toBe(false); + }); + + it('rejects names with hyphens (use formatToPascalCase first)', () => { + expect(isValidComponentName('my-button')).toBe(false); + }); + + it('rejects names with underscores (use formatToPascalCase first)', () => { + expect(isValidComponentName('my_button')).toBe(false); + }); + + it('rejects names with spaces', () => { + expect(isValidComponentName('My Button')).toBe(false); + }); + + it('rejects names with dots', () => { + expect(isValidComponentName('Button.tsx')).toBe(false); + }); + + it('rejects names with slashes', () => { + expect(isValidComponentName('src/Button')).toBe(false); + }); + }); + + describe('typical command flow: format then validate', () => { + it('accepts "button" after formatting', () => { + expect(isValidComponentName(formatToPascalCase('button'))).toBe(true); + }); + + it('accepts "my-card" after formatting', () => { + expect(isValidComponentName(formatToPascalCase('my-card'))).toBe(true); + }); + + it('accepts "myComponent" after formatting', () => { + expect(isValidComponentName(formatToPascalCase('myComponent'))).toBe( + true, + ); + }); + + it('rejects "123" even after formatting (no leading letter)', () => { + expect(isValidComponentName(formatToPascalCase('123'))).toBe(false); + }); + }); +}); + +describe('COMPONENT_NAME_ERROR', () => { + it('is a non-empty string', () => { + expect(typeof COMPONENT_NAME_ERROR).toBe('string'); + expect(COMPONENT_NAME_ERROR.length).toBeGreaterThan(0); + }); +}); diff --git a/src/commands/createComponent.ts b/src/commands/createComponent.ts index 7056ded..6337052 100644 --- a/src/commands/createComponent.ts +++ b/src/commands/createComponent.ts @@ -1,319 +1,138 @@ import * as path from 'node:path'; -import { CodebotError } from '../errors'; +import * as vscode from 'vscode'; +import { readConfig } from '../helpers/config/readConfig'; +import { listTemplates } from '../helpers/template/listTemplates'; +import { processTemplateFolder } from '../helpers/template/processTemplateFolder'; +import { getWorkspaceFolders } from '../helpers/vscode/getWorkspaceFolders'; +import { promptInput } from '../helpers/vscode/promptInput'; +import { promptSelection } from '../helpers/vscode/promptSelection'; +import { showError } from '../helpers/vscode/showError'; +import { showInfo } from '../helpers/vscode/showInfo'; +import { createWorkspaceFolder } from '../helpers/vscode/workspace/createFolder'; +import { workspaceFileExists } from '../helpers/vscode/workspace/fileExists'; +import { writeWorkspaceFile } from '../helpers/vscode/workspace/writeFile'; import { - checkExistsFile, - createFile, - createFolder, - getTextByInputBox, - showMessage, - showSearchDropdown, -} from '../helpers'; -import { ConfigurationManager } from '../managers/ConfigurationManager'; -import { ProjectDetector } from '../managers/ProjectDetector'; -import { TemplateManager } from '../managers/TemplateManager'; -import { - type CommandArgs, - ErrorType, - type ProcessedDirectory, - type ProcessedFile, - type TemplateType, -} from '../types'; -import { PathResolver } from '../utils/PathResolver'; - -export async function createComponent(args: CommandArgs) { - const projectDetector = new ProjectDetector(); - const templateManager = new TemplateManager(); - const pathResolver = new PathResolver(); - const _configurationManager = new ConfigurationManager(); - - try { - // Step 1: Detect project context using ProjectDetector - const currentFolderPath = args?.fsPath; - if (!currentFolderPath) { - throw new CodebotError( - ErrorType.WORKSPACE_NOT_FOUND, - 'No folder path provided for component creation', - { args }, - false, - ); - } - - const projectContext = - await projectDetector.detectProject(currentFolderPath); - - // Step 2: Get component name from user - const componentNameInput = await getTextByInputBox( - 'Enter the component name:', - ); - if (!componentNameInput || componentNameInput.trim() === '') { - throw new CodebotError( - ErrorType.INVALID_COMPONENT_NAME, - 'Component name cannot be empty', - { componentName: componentNameInput }, - true, - ); - } - - const trimmedComponentName = componentNameInput?.trim(); - - // Step 3: Validate component name using PathResolver - if (!pathResolver.validatePath(trimmedComponentName)) { - throw new CodebotError( - ErrorType.INVALID_COMPONENT_NAME, - `Invalid component name: ${trimmedComponentName}. Component names must be valid file/folder names.`, - { componentName: trimmedComponentName }, - true, - ); - } + COMPONENT_NAME_ERROR, + formatToPascalCase, + isValidComponentName, +} from '../utils/validation'; + +const findWorkspaceFolderUri = ( + clickedUri: vscode.Uri, +): vscode.Uri | undefined => { + const folders = getWorkspaceFolders(); + if (!folders) return undefined; + const match = folders.find(f => clickedUri.fsPath.startsWith(f.path)); + return match ? vscode.Uri.file(match.path) : undefined; +}; + +export const createComponent = async ( + clickedUri?: vscode.Uri, +): Promise => { + if (!clickedUri) { + showError('Right-click a folder in the Explorer to create a component.'); + return; + } - // Step 4: Discover templates using TemplateManager with hierarchical discovery - const availableTemplates = await templateManager.discoverTemplates( - projectContext.projectRoot, + const workspaceFolderUri = findWorkspaceFolderUri(clickedUri); + if (!workspaceFolderUri) { + showError( + 'Could not determine the workspace folder for the selected path.', ); + return; + } - if (availableTemplates.length === 0) { - throw new CodebotError( - ErrorType.TEMPLATE_FOLDER_EMPTY, - `No templates found in project. Searched in: ${projectContext.templatePath}`, - { - projectRoot: projectContext.projectRoot, - templatePath: projectContext.templatePath, - isMultiProject: projectContext.isMultiProject, - }, - true, - ); - } - - // Step 5: Select template type - let selectedTemplate: TemplateType | undefined; - - if (availableTemplates.length === 1) { - selectedTemplate = availableTemplates[0]; - } else { - const templateNames = availableTemplates.map(t => t.name); - const selectedTemplateName = await showSearchDropdown( - templateNames, - 'Select template type', - 'Start typing to filter templates...', - ); - - if (!selectedTemplateName) { - throw new CodebotError( - ErrorType.TEMPLATE_NOT_FOUND, - 'No template type selected', - { availableTemplates: templateNames }, - true, - ); - } - - selectedTemplate = availableTemplates.find( - t => t.name === selectedTemplateName, - ); - } - - if (!selectedTemplate) { - throw new CodebotError( - ErrorType.TEMPLATE_NOT_FOUND, - 'Selected template not found', - { availableTemplates }, - false, - ); - } - - // Step 6: Resolve target path using PathResolver - const targetBasePath = pathResolver.resolveTargetPath( - projectContext, - trimmedComponentName, - currentFolderPath, - ); + const config = readConfig(workspaceFolderUri); + const templatesUri = vscode.Uri.joinPath( + workspaceFolderUri, + config.templatesFolderPath, + ); - // Step 7: Process templates hierarchically and create files - const templateStructure = await templateManager.scanTemplateStructure( - selectedTemplate.path, + const templatesResult = await listTemplates(templatesUri); + if (!templatesResult.success) { + showError( + `Could not read templates folder '${config.templatesFolderPath}'.`, ); + return; + } - if ( - templateStructure.files.length === 0 && - templateStructure.directories.length === 0 - ) { - throw new CodebotError( - ErrorType.TEMPLATE_FOLDER_EMPTY, - `Template folder is empty: ${selectedTemplate.path}`, - { - templatePath: selectedTemplate.path, - templateName: selectedTemplate.name, - }, - false, - ); - } - - // Create target directory if it doesn't exist - if (!checkExistsFile(targetBasePath)) { - createFolder(targetBasePath); - } - - // Process the template hierarchy - const processedTemplate = await templateManager.processTemplateHierarchy( - templateStructure, - trimmedComponentName, - ); + if (templatesResult.value.length === 0) { + showError(`No templates found in '${config.templatesFolderPath}'.`); + return; + } - // Track creation and skipping statistics - const creationReport = { - filesCreated: 0, - filesSkipped: 0, - directoriesCreated: 0, - createdFiles: [] as string[], - skippedFiles: [] as string[], - }; + let templateName: string | undefined; + if (templatesResult.value.length === 1) { + templateName = templatesResult.value[0]; + } else { + templateName = await promptSelection(templatesResult.value, { + placeholder: 'Select a template', + }); + } + if (!templateName) return; + + const rawName = await promptInput({ + prompt: 'Component name', + placeholder: 'e.g. Button', + validateInput: v => + isValidComponentName(formatToPascalCase(v)) + ? undefined + : COMPONENT_NAME_ERROR, + }); + if (!rawName) return; + + const componentName = formatToPascalCase(rawName.trim()); + const templateFolderUri = vscode.Uri.joinPath(templatesUri, templateName); + + const processedResult = await processTemplateFolder( + templateFolderUri, + templateName, + componentName, + ); + if (!processedResult.success) { + showError(`Failed to process template: ${processedResult.error.message}`); + return; + } - // Process root-level files - await processFiles(processedTemplate.files, targetBasePath, creationReport); + let created = 0; + let skipped = 0; - // Process directories recursively - await processDirectories( - processedTemplate.directories, - targetBasePath, - creationReport, + for (const file of processedResult.value) { + const fileUri = vscode.Uri.joinPath( + clickedUri, + componentName, + file.targetRelativePath, ); - const filesCreated = creationReport.filesCreated; - const filesSkipped = creationReport.filesSkipped; - - // Step 8: Show success message with details - let successMessage = `Component '${trimmedComponentName}' created successfully!`; - if (filesCreated > 0) { - successMessage += ` (${filesCreated} files created`; - if (creationReport.directoriesCreated > 0) { - successMessage += `, ${creationReport.directoriesCreated} directories created`; - } - if (filesSkipped > 0) { - successMessage += `, ${filesSkipped} files skipped`; - } - successMessage += ')'; - } - - if (projectContext.isMultiProject && projectContext.projectName) { - successMessage += ` in project '${projectContext.projectName}'`; + if (await workspaceFileExists(fileUri)) { + skipped++; + continue; } - showMessage(successMessage, 'information'); - } catch (error) { - // Handle multi-project specific errors - if (error instanceof CodebotError) { - let errorMessage = error.message; - - // Add context for multi-project scenarios - const details = error.details as - | { - isMultiProject?: boolean; - projectRoot?: string; - workspaceRoot?: string; - } - | undefined; - if ( - error.type === ErrorType.TEMPLATE_FOLDER_EMPTY && - details?.isMultiProject - ) { - errorMessage += - '\n\nTip: In multi-project workspaces, templates can be placed in:'; - errorMessage += `\n- Project-specific: ${details.projectRoot}/templates`; - errorMessage += `\n- Workspace-wide: ${details.workspaceRoot}/templates`; - } - - if (error.type === ErrorType.PROJECT_DETECTION_ERROR) { - errorMessage += - '\n\nTip: Ensure you are running the command from within a valid project folder.'; - } - - showMessage(errorMessage, 'error'); - } else if (error instanceof Error) { - // Handle unexpected errors - const unexpectedError = new CodebotError( - ErrorType.FILE_SYSTEM_ERROR, - `Unexpected error during component creation: ${error.message}`, - { originalError: error.message, stack: error.stack }, - false, - ); - - showMessage(unexpectedError.message, 'error'); - } else { - // Handle unknown errors - showMessage( - 'An unknown error occurred during component creation', - 'error', + const parentUri = vscode.Uri.file(path.dirname(fileUri.fsPath)); + const folderResult = await createWorkspaceFolder(parentUri); + if (!folderResult.success) { + showError( + `Failed to create folder for '${file.targetRelativePath}': ${folderResult.error.message}`, ); + return; } - } -} - -// Helper function to process files hierarchically -async function processFiles( - files: ProcessedFile[], - basePath: string, - creationReport: { - filesCreated: number; - filesSkipped: number; - directoriesCreated: number; - createdFiles: string[]; - skippedFiles: string[]; - }, -): Promise { - for (const file of files) { - const targetFilePath = path.join(basePath, file.targetPath); - // Skip if file already exists (don't overwrite) - if (checkExistsFile(targetFilePath)) { - creationReport.filesSkipped++; - creationReport.skippedFiles.push(file.targetPath); - continue; - } - - try { - // Create the file - createFile(targetFilePath, file.content); - creationReport.filesCreated++; - creationReport.createdFiles.push(file.targetPath); - } catch (error) { - throw new CodebotError( - ErrorType.TEMPLATE_PROCESSING_ERROR, - `Failed to create file: ${file.targetPath}`, - { targetPath: targetFilePath, error }, - false, + const writeResult = await writeWorkspaceFile(fileUri, file.content); + if (!writeResult.success) { + showError( + `Failed to write '${file.targetRelativePath}': ${writeResult.error.message}`, ); + return; } - } -} -// Helper function to process directories hierarchically -async function processDirectories( - directories: ProcessedDirectory[], - basePath: string, - creationReport: { - filesCreated: number; - filesSkipped: number; - directoriesCreated: number; - createdFiles: string[]; - skippedFiles: string[]; - }, -): Promise { - for (const directory of directories) { - const targetDirPath = path.join(basePath, directory.targetPath); - - // Create directory if it doesn't exist - if (!checkExistsFile(targetDirPath)) { - createFolder(targetDirPath); - creationReport.directoriesCreated++; - } + created++; + } - // Process files in this directory - await processFiles(directory.files, basePath, creationReport); + const msg = + skipped > 0 + ? `'${componentName}' created — ${created} file(s) written, ${skipped} skipped` + : `'${componentName}' created — ${created} file(s) written`; - // Process subdirectories recursively - await processDirectories( - directory.subdirectories, - basePath, - creationReport, - ); - } -} + showInfo(msg); +}; diff --git a/src/commands/index.ts b/src/commands/index.ts deleted file mode 100644 index e8702e6..0000000 --- a/src/commands/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './createComponent'; -export * from './updateComponent'; diff --git a/src/commands/updateComponent.ts b/src/commands/updateComponent.ts index 1abe5b2..00f6576 100644 --- a/src/commands/updateComponent.ts +++ b/src/commands/updateComponent.ts @@ -1,324 +1,134 @@ import * as path from 'node:path'; -import { CodebotError } from '../errors'; -import { - checkExistsFile, - createFile, - createFolder, - showMessage, - showSearchDropdown, -} from '../helpers'; -import { ConfigurationManager } from '../managers/ConfigurationManager'; -import { ProjectDetector } from '../managers/ProjectDetector'; -import { TemplateManager } from '../managers/TemplateManager'; -import { - type CommandArgs, - ErrorType, - type ProcessedDirectory, - type ProcessedFile, - type TemplateType, -} from '../types'; -import { PathResolver } from '../utils/PathResolver'; - -export async function updateComponent(args: CommandArgs) { - const projectDetector = new ProjectDetector(); - const templateManager = new TemplateManager(); - const pathResolver = new PathResolver(); - const _configurationManager = new ConfigurationManager(); - - try { - // Step 1: Detect project context using ProjectDetector - const currentFolderPath = args?.fsPath; - if (!currentFolderPath) { - throw new CodebotError( - ErrorType.WORKSPACE_NOT_FOUND, - 'No folder path provided for component update', - { args }, - false, - ); - } - - const projectContext = - await projectDetector.detectProject(currentFolderPath); - - // Step 2: Extract component name from folder path - const componentName = path.basename(currentFolderPath); - if (!componentName || componentName.trim() === '') { - throw new CodebotError( - ErrorType.INVALID_COMPONENT_NAME, - 'Cannot determine component name from folder path', - { currentFolderPath }, - true, - ); - } - - // Step 3: Validate component name using PathResolver - if (!pathResolver.validatePath(componentName)) { - throw new CodebotError( - ErrorType.INVALID_COMPONENT_NAME, - `Invalid component name: ${componentName}. Component names must be valid file/folder names.`, - { componentName, folderPath: currentFolderPath }, - true, - ); - } - - // Step 4: Discover templates using TemplateManager with hierarchical discovery - const availableTemplates = await templateManager.discoverTemplates( - projectContext.projectRoot, - ); - - if (availableTemplates.length === 0) { - throw new CodebotError( - ErrorType.TEMPLATE_FOLDER_EMPTY, - `No templates found in project. Searched in: ${projectContext.templatePath}`, - { - projectRoot: projectContext.projectRoot, - templatePath: projectContext.templatePath, - isMultiProject: projectContext.isMultiProject, - }, - true, - ); - } - - // Step 5: Select template type - let selectedTemplate: TemplateType | undefined; - - if (availableTemplates.length === 1) { - selectedTemplate = availableTemplates[0]; - } else { - const templateNames = availableTemplates.map(t => t.name); - const selectedTemplateName = await showSearchDropdown( - templateNames, - 'Select template type', - 'Start typing to filter templates...', - ); - - if (!selectedTemplateName) { - throw new CodebotError( - ErrorType.TEMPLATE_NOT_FOUND, - 'No template type selected', - { availableTemplates: templateNames }, - true, - ); - } - - selectedTemplate = availableTemplates.find( - t => t.name === selectedTemplateName, - ); - } - - if (!selectedTemplate) { - throw new CodebotError( - ErrorType.TEMPLATE_NOT_FOUND, - 'Selected template not found', - { availableTemplates }, - false, - ); - } - - // Step 6: Validate target path is within project boundaries - const targetBasePath = currentFolderPath; - - // Ensure the target path is within project boundaries using PathResolver - try { - pathResolver.resolveTargetPath( - projectContext, - componentName, - path.dirname(currentFolderPath), - ); - } catch (pathError) { - throw new CodebotError( - ErrorType.FILE_SYSTEM_ERROR, - `Target path validation failed: ${pathError instanceof Error ? pathError.message : 'Unknown error'}`, - { targetPath: targetBasePath, projectContext }, - false, - ); - } +import * as vscode from 'vscode'; +import { readConfig } from '../helpers/config/readConfig'; +import { listTemplates } from '../helpers/template/listTemplates'; +import { processTemplateFolder } from '../helpers/template/processTemplateFolder'; +import { getWorkspaceFolders } from '../helpers/vscode/getWorkspaceFolders'; +import { promptSelection } from '../helpers/vscode/promptSelection'; +import { showError } from '../helpers/vscode/showError'; +import { showInfo } from '../helpers/vscode/showInfo'; +import { createWorkspaceFolder } from '../helpers/vscode/workspace/createFolder'; +import { workspaceFileExists } from '../helpers/vscode/workspace/fileExists'; +import { writeWorkspaceFile } from '../helpers/vscode/workspace/writeFile'; +import { formatToPascalCase, isValidComponentName } from '../utils/validation'; + +const findWorkspaceFolderUri = ( + clickedUri: vscode.Uri, +): vscode.Uri | undefined => { + const folders = getWorkspaceFolders(); + if (!folders) return undefined; + const match = folders.find(f => clickedUri.fsPath.startsWith(f.path)); + return match ? vscode.Uri.file(match.path) : undefined; +}; + +export const updateComponent = async ( + clickedUri?: vscode.Uri, +): Promise => { + if (!clickedUri) { + showError('Right-click a component folder in the Explorer to update it.'); + return; + } - // Step 7: Process templates hierarchically and update files (only create missing files) - const templateStructure = await templateManager.scanTemplateStructure( - selectedTemplate.path, + const rawName = path.basename(clickedUri.fsPath); + const componentName = formatToPascalCase(rawName); + if (!isValidComponentName(componentName)) { + showError( + `'${rawName}' cannot be converted to a valid component name. Right-click directly on the component folder.`, ); + return; + } - if ( - templateStructure.files.length === 0 && - templateStructure.directories.length === 0 - ) { - throw new CodebotError( - ErrorType.TEMPLATE_FOLDER_EMPTY, - `Template folder is empty: ${selectedTemplate.path}`, - { - templatePath: selectedTemplate.path, - templateName: selectedTemplate.name, - }, - false, - ); - } - - // Ensure target directory exists - if (!checkExistsFile(targetBasePath)) { - createFolder(targetBasePath); - } - - // Process the template hierarchy - const processedTemplate = await templateManager.processTemplateHierarchy( - templateStructure, - componentName, + const workspaceFolderUri = findWorkspaceFolderUri(clickedUri); + if (!workspaceFolderUri) { + showError( + 'Could not determine the workspace folder for the selected path.', ); + return; + } - // Track creation and skipping statistics - const creationReport = { - filesCreated: 0, - filesSkipped: 0, - directoriesCreated: 0, - createdFiles: [] as string[], - skippedFiles: [] as string[], - }; - - // Process root-level files - await processFiles(processedTemplate.files, targetBasePath, creationReport); + const config = readConfig(workspaceFolderUri); + const templatesUri = vscode.Uri.joinPath( + workspaceFolderUri, + config.templatesFolderPath, + ); - // Process directories recursively - await processDirectories( - processedTemplate.directories, - targetBasePath, - creationReport, + const templatesResult = await listTemplates(templatesUri); + if (!templatesResult.success) { + showError( + `Could not read templates folder '${config.templatesFolderPath}'.`, ); + return; + } - // Step 8: Show success message with details - let successMessage = `Component '${componentName}' updated successfully!`; - if (creationReport.filesCreated > 0) { - successMessage += ` (${creationReport.filesCreated} files added`; - if (creationReport.directoriesCreated > 0) { - successMessage += `, ${creationReport.directoriesCreated} directories created`; - } - if (creationReport.filesSkipped > 0) { - successMessage += `, ${creationReport.filesSkipped} files already exist`; - } - successMessage += ')'; - } else if (creationReport.filesSkipped > 0) { - successMessage = `Component '${componentName}' is already up to date (${creationReport.filesSkipped} files already exist)`; - } - - if (projectContext.isMultiProject && projectContext.projectName) { - successMessage += ` in project '${projectContext.projectName}'`; - } - - showMessage(successMessage, 'information'); - } catch (error) { - // Handle multi-project specific errors - if (error instanceof CodebotError) { - let errorMessage = error.message; - - // Add context for multi-project scenarios - const details = - typeof error.details === 'object' && error.details !== null - ? (error.details as Record) - : null; - if ( - error.type === ErrorType.TEMPLATE_FOLDER_EMPTY && - details?.isMultiProject - ) { - errorMessage += - '\n\nTip: In multi-project workspaces, templates can be placed in:'; - errorMessage += `\n- Project-specific: ${details.projectRoot}/templates`; - errorMessage += `\n- Workspace-wide: ${details.workspaceRoot}/templates`; - } - - if (error.type === ErrorType.PROJECT_DETECTION_ERROR) { - errorMessage += - '\n\nTip: Ensure you are running the command from within a valid project folder.'; - } - - if (error.type === ErrorType.INVALID_COMPONENT_NAME) { - errorMessage += - '\n\nTip: For update operations, make sure you are right-clicking on a component folder.'; - } - - showMessage(errorMessage, 'error'); - } else if (error instanceof Error) { - // Handle unexpected errors - const unexpectedError = new CodebotError( - ErrorType.FILE_SYSTEM_ERROR, - `Unexpected error during component update: ${error.message}`, - { originalError: error.message, stack: error.stack }, - false, - ); + if (templatesResult.value.length === 0) { + showError(`No templates found in '${config.templatesFolderPath}'.`); + return; + } - showMessage(unexpectedError.message, 'error'); - } else { - // Handle unknown errors - showMessage('An unknown error occurred during component update', 'error'); - } + let templateName: string | undefined; + if (templatesResult.value.length === 1) { + templateName = templatesResult.value[0]; + } else { + templateName = await promptSelection(templatesResult.value, { + placeholder: 'Select a template', + }); + } + if (!templateName) return; + + const templateFolderUri = vscode.Uri.joinPath(templatesUri, templateName); + + const processedResult = await processTemplateFolder( + templateFolderUri, + templateName, + componentName, + ); + if (!processedResult.success) { + showError(`Failed to process template: ${processedResult.error.message}`); + return; } -} -// Helper function to process files hierarchically -async function processFiles( - files: ProcessedFile[], - basePath: string, - creationReport: { - filesCreated: number; - filesSkipped: number; - directoriesCreated: number; - createdFiles: string[]; - skippedFiles: string[]; - }, -): Promise { - for (const file of files) { - const targetFilePath = path.join(basePath, file.targetPath); + let created = 0; + let skipped = 0; - // Skip if file already exists (key difference from createComponent - don't overwrite) - if (checkExistsFile(targetFilePath)) { - creationReport.filesSkipped++; - creationReport.skippedFiles.push(file.targetPath); + for (const file of processedResult.value) { + const fileUri = vscode.Uri.joinPath(clickedUri, file.targetRelativePath); + + if (await workspaceFileExists(fileUri)) { + skipped++; continue; } - try { - // Create the file - createFile(targetFilePath, file.content); - creationReport.filesCreated++; - creationReport.createdFiles.push(file.targetPath); - } catch (error) { - throw new CodebotError( - ErrorType.TEMPLATE_PROCESSING_ERROR, - `Failed to create file: ${file.targetPath}`, - { targetPath: targetFilePath, error }, - false, + const parentUri = vscode.Uri.file(path.dirname(fileUri.fsPath)); + const folderResult = await createWorkspaceFolder(parentUri); + if (!folderResult.success) { + showError( + `Failed to create folder for '${file.targetRelativePath}': ${folderResult.error.message}`, ); + return; } - } -} -// Helper function to process directories hierarchically -async function processDirectories( - directories: ProcessedDirectory[], - basePath: string, - creationReport: { - filesCreated: number; - filesSkipped: number; - directoriesCreated: number; - createdFiles: string[]; - skippedFiles: string[]; - }, -): Promise { - for (const directory of directories) { - const targetDirPath = path.join(basePath, directory.targetPath); - - // Create directory if it doesn't exist - if (!checkExistsFile(targetDirPath)) { - createFolder(targetDirPath); - creationReport.directoriesCreated++; + const writeResult = await writeWorkspaceFile(fileUri, file.content); + if (!writeResult.success) { + showError( + `Failed to write '${file.targetRelativePath}': ${writeResult.error.message}`, + ); + return; } - // Process files in this directory - await processFiles(directory.files, basePath, creationReport); + created++; + } - // Process subdirectories recursively - await processDirectories( - directory.subdirectories, - basePath, - creationReport, + if (created === 0 && skipped > 0) { + showInfo( + `'${componentName}' is already up to date — all ${skipped} file(s) exist.`, ); + return; } -} + + const msg = + skipped > 0 + ? `'${componentName}' updated — ${created} file(s) added, ${skipped} skipped` + : `'${componentName}' updated — ${created} file(s) added`; + + showInfo(msg); +}; diff --git a/src/constants/index.ts b/src/constants/index.ts deleted file mode 100644 index fa29664..0000000 --- a/src/constants/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -export const DEFAULT_TEMPLATE_FOLDER = 'templates'; -export const CONFIG_FILE_NAME = 'codebot.config.json'; -export const TEMPLATE_FILE_EXTENSION = '.hbs'; - -export const DEFAULT_CONFIGURATION = { - templateFolderPath: DEFAULT_TEMPLATE_FOLDER, - multiProject: { - enabled: true, - projectDetection: 'auto' as const, - }, -}; - -export const SUPPORTED_TEMPLATE_EXTENSIONS = ['.hbs']; -export const COMPONENT_NAME_REGEX = /^[a-zA-Z][a-zA-Z0-9_-]*$/; - -export const ERROR_MESSAGES = { - WORKSPACE_NOT_FOUND: 'Workspace path not found!', - INVALID_COMPONENT_NAME: 'Invalid component name!', - TEMPLATE_FOLDER_EMPTY: 'Templates folder is empty!', - TEMPLATE_NOT_FOUND: 'Template folder not found!', - INVALID_TEMPLATE_TYPE: 'Invalid template type!', -} as const; diff --git a/src/errors/CodebotError.ts b/src/errors/CodebotError.ts deleted file mode 100644 index c78b0d7..0000000 --- a/src/errors/CodebotError.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { ErrorType } from '../types'; - -export class CodebotError extends Error { - public readonly type: ErrorType; - public readonly details?: unknown; - public readonly recoverable: boolean; - - constructor( - type: ErrorType, - message: string, - details?: unknown, - recoverable = false, - ) { - super(message); - this.name = 'CodebotError'; - this.type = type; - this.details = details; - this.recoverable = recoverable; - } - - static workspaceNotFound(): CodebotError { - return new CodebotError( - ErrorType.WORKSPACE_NOT_FOUND, - 'Workspace path not found!', - undefined, - false, - ); - } - - static invalidComponentName(name?: string): CodebotError { - return new CodebotError( - ErrorType.INVALID_COMPONENT_NAME, - 'Invalid component name!', - { providedName: name }, - true, - ); - } - - static templateFolderEmpty(path: string): CodebotError { - return new CodebotError( - ErrorType.TEMPLATE_FOLDER_EMPTY, - 'Templates folder is empty!', - { templatePath: path }, - true, - ); - } - - static templateNotFound(templateType: string): CodebotError { - return new CodebotError( - ErrorType.TEMPLATE_NOT_FOUND, - 'Template folder not found!', - { templateType }, - true, - ); - } - - static fileSystemError( - operation: string, - path: string, - originalError?: Error, - ): CodebotError { - return new CodebotError( - ErrorType.FILE_SYSTEM_ERROR, - `File system error during ${operation}`, - { path, originalError: originalError?.message }, - false, - ); - } - - static templateProcessingError( - templatePath: string, - originalError?: Error, - ): CodebotError { - return new CodebotError( - ErrorType.TEMPLATE_PROCESSING_ERROR, - 'Error processing template', - { templatePath, originalError: originalError?.message }, - true, - ); - } - - static projectDetectionError( - folderPath: string, - originalError?: Error, - ): CodebotError { - return new CodebotError( - ErrorType.PROJECT_DETECTION_ERROR, - 'Error detecting project context', - { folderPath, originalError: originalError?.message }, - true, - ); - } - - static configurationError( - configPath: string, - originalError?: Error, - ): CodebotError { - return new CodebotError( - ErrorType.CONFIGURATION_ERROR, - 'Error loading configuration', - { configPath, originalError: originalError?.message }, - true, - ); - } - - static pathValidationError(path: string, reason: string): CodebotError { - return new CodebotError( - ErrorType.FILE_SYSTEM_ERROR, - `Invalid path: ${reason}`, - { path, reason }, - true, - ); - } - - static pathTraversalError(path: string): CodebotError { - return new CodebotError( - ErrorType.FILE_SYSTEM_ERROR, - 'Path traversal attempt detected', - { path }, - false, - ); - } - - static pathOutsideBoundariesError( - path: string, - projectRoot: string, - ): CodebotError { - return new CodebotError( - ErrorType.FILE_SYSTEM_ERROR, - 'Path is outside project boundaries', - { path, projectRoot }, - false, - ); - } -} diff --git a/src/errors/index.ts b/src/errors/index.ts deleted file mode 100644 index 72c12f8..0000000 --- a/src/errors/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './CodebotError'; diff --git a/src/extension.ts b/src/extension.ts index 8a7da6e..1da4cf9 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,17 +1,16 @@ import * as vscode from 'vscode'; -import { createComponent, updateComponent } from './commands'; +import { createComponent } from './commands/createComponent'; +import { updateComponent } from './commands/updateComponent'; export function activate(context: vscode.ExtensionContext) { - const disposable = [ + context.subscriptions.push( vscode.commands.registerCommand('codebot.createComponent', args => createComponent(args), ), vscode.commands.registerCommand('codebot.updateComponent', args => updateComponent(args), ), - ]; - - context.subscriptions.push(...disposable); + ); } export function deactivate() {} diff --git a/src/helpers/buildTemplate.ts b/src/helpers/buildTemplate.ts deleted file mode 100644 index b274684..0000000 --- a/src/helpers/buildTemplate.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as handlebars from 'handlebars'; -import { formatToPascalCase } from './formatToPascalCase'; -import { readFile } from './readFile'; - -export function buildTemplate(path: string, componentName: string) { - const currentTemplate = handlebars.compile(readFile(path)); - - return currentTemplate({ - name: formatToPascalCase(componentName), - }); -} diff --git a/src/helpers/checkExistsFile.ts b/src/helpers/checkExistsFile.ts deleted file mode 100644 index a47dcd0..0000000 --- a/src/helpers/checkExistsFile.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { readFile } from './readFile'; - -export function checkExistsFile(path: string) { - try { - readFile(path); - return true; - } catch (_error) { - return false; - } -} diff --git a/src/helpers/checkExistsFolder.ts b/src/helpers/checkExistsFolder.ts deleted file mode 100644 index 705ee8c..0000000 --- a/src/helpers/checkExistsFolder.ts +++ /dev/null @@ -1,5 +0,0 @@ -import * as fs from 'node:fs'; - -export function checkExistsFolder(path: string) { - return fs.existsSync(path); -} diff --git a/src/helpers/config/readConfig.ts b/src/helpers/config/readConfig.ts new file mode 100644 index 0000000..adf2392 --- /dev/null +++ b/src/helpers/config/readConfig.ts @@ -0,0 +1,16 @@ +import * as vscode from 'vscode'; +import { + CONFIG_KEY_TEMPLATES_FOLDER, + CONFIG_SECTION, + type CodebotConfig, + DEFAULT_TEMPLATES_FOLDER, +} from '../../types/Config'; + +export const readConfig = (scope?: vscode.Uri): CodebotConfig => { + const config = vscode.workspace.getConfiguration(CONFIG_SECTION, scope); + return { + templatesFolderPath: + config.get(CONFIG_KEY_TEMPLATES_FOLDER) ?? + DEFAULT_TEMPLATES_FOLDER, + }; +}; diff --git a/src/helpers/createFile.ts b/src/helpers/createFile.ts deleted file mode 100644 index 4c69b43..0000000 --- a/src/helpers/createFile.ts +++ /dev/null @@ -1,5 +0,0 @@ -import * as fs from 'node:fs'; - -export function createFile(path: string, data: string) { - fs.writeFileSync(path, data, { encoding: 'utf-8' }); -} diff --git a/src/helpers/createFolder.ts b/src/helpers/createFolder.ts deleted file mode 100644 index 5192cf3..0000000 --- a/src/helpers/createFolder.ts +++ /dev/null @@ -1,5 +0,0 @@ -import * as fs from 'node:fs'; - -export function createFolder(path: string) { - fs.mkdirSync(path, { recursive: true }); -} diff --git a/src/helpers/filterTemplatesFiles.ts b/src/helpers/filterTemplatesFiles.ts deleted file mode 100644 index 71d2d5c..0000000 --- a/src/helpers/filterTemplatesFiles.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function filterTemplatesFiles(files: string[]) { - return files.filter(file => file.includes('.hbs')); -} diff --git a/src/helpers/formatTemplateName.ts b/src/helpers/formatTemplateName.ts deleted file mode 100644 index 8dcd91d..0000000 --- a/src/helpers/formatTemplateName.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { formatToPascalCase } from './formatToPascalCase'; - -export function formatTemplateName( - name: string, - templateType: string, - componentName: string, -) { - return name - .replace(templateType, formatToPascalCase(componentName)) - .replace('.hbs', ''); -} diff --git a/src/helpers/formatToPascalCase.ts b/src/helpers/formatToPascalCase.ts deleted file mode 100644 index 4395695..0000000 --- a/src/helpers/formatToPascalCase.ts +++ /dev/null @@ -1,6 +0,0 @@ -export function formatToPascalCase(str: string) { - return str - .split(' ') - .map(item => item.charAt(0).toUpperCase() + item.slice(1)) - .join(''); -} diff --git a/src/helpers/fs/createFile.ts b/src/helpers/fs/createFile.ts new file mode 100644 index 0000000..38289f1 --- /dev/null +++ b/src/helpers/fs/createFile.ts @@ -0,0 +1,23 @@ +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { type AppError, makeError } from '../../types/AppError'; +import { err, ok, type Result } from '../../types/Result'; + +export const createFile = ( + filePath: string, + content: string, +): Result => { + try { + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, content, 'utf-8'); + return ok(undefined); + } catch (cause) { + return err( + makeError( + 'FILE_WRITE_ERROR', + `Failed to create file: ${filePath}`, + cause, + ), + ); + } +}; diff --git a/src/helpers/fs/createFolder.ts b/src/helpers/fs/createFolder.ts new file mode 100644 index 0000000..5801fbd --- /dev/null +++ b/src/helpers/fs/createFolder.ts @@ -0,0 +1,18 @@ +import * as fs from 'node:fs'; +import { type AppError, makeError } from '../../types/AppError'; +import { err, ok, type Result } from '../../types/Result'; + +export const createFolder = (folderPath: string): Result => { + try { + fs.mkdirSync(folderPath, { recursive: true }); + return ok(undefined); + } catch (cause) { + return err( + makeError( + 'FOLDER_CREATE_ERROR', + `Failed to create folder: ${folderPath}`, + cause, + ), + ); + } +}; diff --git a/src/helpers/fs/fileExists.ts b/src/helpers/fs/fileExists.ts new file mode 100644 index 0000000..2fac992 --- /dev/null +++ b/src/helpers/fs/fileExists.ts @@ -0,0 +1,10 @@ +import * as fs from 'node:fs'; + +export const fileExists = (filePath: string): boolean => { + try { + fs.accessSync(filePath, fs.constants.F_OK); + return true; + } catch { + return false; + } +}; diff --git a/src/helpers/fs/folderExists.ts b/src/helpers/fs/folderExists.ts new file mode 100644 index 0000000..bd7b440 --- /dev/null +++ b/src/helpers/fs/folderExists.ts @@ -0,0 +1,9 @@ +import * as fs from 'node:fs'; + +export const folderExists = (folderPath: string): boolean => { + try { + return fs.statSync(folderPath).isDirectory(); + } catch { + return false; + } +}; diff --git a/src/helpers/fs/index.ts b/src/helpers/fs/index.ts new file mode 100644 index 0000000..ab3d852 --- /dev/null +++ b/src/helpers/fs/index.ts @@ -0,0 +1,6 @@ +export { createFile } from './createFile'; +export { createFolder } from './createFolder'; +export { fileExists } from './fileExists'; +export { folderExists } from './folderExists'; +export { listFiles } from './listFiles'; +export { readFile } from './readFile'; diff --git a/src/helpers/fs/listFiles.ts b/src/helpers/fs/listFiles.ts new file mode 100644 index 0000000..f7983d4 --- /dev/null +++ b/src/helpers/fs/listFiles.ts @@ -0,0 +1,17 @@ +import * as fs from 'node:fs'; +import { type AppError, makeError } from '../../types/AppError'; +import { err, ok, type Result } from '../../types/Result'; + +export const listFiles = (folderPath: string): Result => { + try { + return ok(fs.readdirSync(folderPath)); + } catch (cause) { + return err( + makeError( + 'FOLDER_LIST_ERROR', + `Failed to list files in: ${folderPath}`, + cause, + ), + ); + } +}; diff --git a/src/helpers/fs/readFile.ts b/src/helpers/fs/readFile.ts new file mode 100644 index 0000000..c20ee36 --- /dev/null +++ b/src/helpers/fs/readFile.ts @@ -0,0 +1,13 @@ +import * as fs from 'node:fs'; +import { type AppError, makeError } from '../../types/AppError'; +import { err, ok, type Result } from '../../types/Result'; + +export const readFile = (filePath: string): Result => { + try { + return ok(fs.readFileSync(filePath, 'utf-8')); + } catch (cause) { + return err( + makeError('FILE_READ_ERROR', `Failed to read file: ${filePath}`, cause), + ); + } +}; diff --git a/src/helpers/getConfigurationFile.ts b/src/helpers/getConfigurationFile.ts deleted file mode 100644 index 1099cb7..0000000 --- a/src/helpers/getConfigurationFile.ts +++ /dev/null @@ -1,24 +0,0 @@ -import * as path from 'node:path'; -import type { ConfigurationFile } from '../types'; -import { getWorkspaceFolder } from './getWorkspaceFolder'; -import { readFile } from './readFile'; - -export function getConfigurationFile( - projectPath?: string, -): ConfigurationFile | null { - try { - const workspaceFolderPath = getWorkspaceFolder(); - const targetPath = projectPath || workspaceFolderPath; - - if (targetPath) { - const file = readFile(path.resolve(targetPath, 'codebot.config.json')); - - const configurationFile = JSON.parse(file); - return configurationFile; - } - - return null; - } catch (_error) { - return null; - } -} diff --git a/src/helpers/getFilesByFolder.ts b/src/helpers/getFilesByFolder.ts deleted file mode 100644 index e3f2fd5..0000000 --- a/src/helpers/getFilesByFolder.ts +++ /dev/null @@ -1,5 +0,0 @@ -import * as fs from 'node:fs'; - -export function getFilesByFolder(path: string) { - return fs.readdirSync(path); -} diff --git a/src/helpers/getTextByInputBox.ts b/src/helpers/getTextByInputBox.ts deleted file mode 100644 index 4c0dc07..0000000 --- a/src/helpers/getTextByInputBox.ts +++ /dev/null @@ -1,8 +0,0 @@ -import * as vscode from 'vscode'; - -export async function getTextByInputBox(label: string) { - return vscode.window.showInputBox({ - prompt: label, - ignoreFocusOut: true, - }); -} diff --git a/src/helpers/getWorkspaceFolder.ts b/src/helpers/getWorkspaceFolder.ts deleted file mode 100644 index 42f3b06..0000000 --- a/src/helpers/getWorkspaceFolder.ts +++ /dev/null @@ -1,5 +0,0 @@ -import * as vscode from 'vscode'; - -export function getWorkspaceFolder() { - return vscode?.workspace?.workspaceFolders?.[0]?.uri?.fsPath; -} diff --git a/src/helpers/index.ts b/src/helpers/index.ts index 13d9b13..9559890 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -1,15 +1,3 @@ -export * from './buildTemplate'; -export * from './checkExistsFile'; -export * from './checkExistsFolder'; -export * from './createFile'; -export * from './createFolder'; -export * from './filterTemplatesFiles'; -export * from './formatTemplateName'; -export * from './formatToPascalCase'; -export * from './getConfigurationFile'; -export * from './getFilesByFolder'; -export * from './getTextByInputBox'; -export * from './getWorkspaceFolder'; -export * from './readFile'; -export * from './showMessage'; -export * from './showSearchDropdown'; +export * from './fs'; +export * from './template'; +export * from './vscode'; diff --git a/src/helpers/readFile.ts b/src/helpers/readFile.ts deleted file mode 100644 index 10cddee..0000000 --- a/src/helpers/readFile.ts +++ /dev/null @@ -1,5 +0,0 @@ -import * as fs from 'node:fs'; - -export function readFile(path: string) { - return fs.readFileSync(path, 'utf8'); -} diff --git a/src/helpers/showMessage.ts b/src/helpers/showMessage.ts deleted file mode 100644 index 08e8ce3..0000000 --- a/src/helpers/showMessage.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as vscode from 'vscode'; - -export function showMessage(message: string, type: 'information' | 'error') { - if (type === 'information') { - vscode.window.showInformationMessage(message); - } - - if (type === 'error') { - vscode.window.showErrorMessage(message); - } -} diff --git a/src/helpers/showSearchDropdown.ts b/src/helpers/showSearchDropdown.ts deleted file mode 100644 index f1e67bd..0000000 --- a/src/helpers/showSearchDropdown.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as vscode from 'vscode'; - -export async function showSearchDropdown( - options: string[], - title: string, - placeholder: string, -) { - return vscode.window.showQuickPick(options, { - title, - canPickMany: false, - placeHolder: placeholder, - }); -} diff --git a/src/helpers/template/compileTemplate.ts b/src/helpers/template/compileTemplate.ts new file mode 100644 index 0000000..a843700 --- /dev/null +++ b/src/helpers/template/compileTemplate.ts @@ -0,0 +1,17 @@ +import * as Handlebars from 'handlebars'; +import { type AppError, makeError } from '../../types/AppError'; +import { err, ok, type Result } from '../../types/Result'; + +export const compileTemplate = ( + templateContent: string, + data: Record, +): Result => { + try { + const template = Handlebars.compile(templateContent); + return ok(template(data)); + } catch (cause) { + return err( + makeError('TEMPLATE_COMPILE_ERROR', 'Failed to compile template', cause), + ); + } +}; diff --git a/src/helpers/template/index.ts b/src/helpers/template/index.ts new file mode 100644 index 0000000..6d6bdaf --- /dev/null +++ b/src/helpers/template/index.ts @@ -0,0 +1,5 @@ +export { compileTemplate } from './compileTemplate'; +export { isTemplateFile } from './isTemplateFile'; +export { removeTemplateExtension } from './removeTemplateExtension'; +export { renameTemplateFile } from './renameTemplateFile'; +export { resolveTargetPath } from './resolveTargetPath'; diff --git a/src/helpers/template/isTemplateFile.ts b/src/helpers/template/isTemplateFile.ts new file mode 100644 index 0000000..5b155a6 --- /dev/null +++ b/src/helpers/template/isTemplateFile.ts @@ -0,0 +1,2 @@ +export const isTemplateFile = (filename: string): boolean => + filename.endsWith('.hbs'); diff --git a/src/helpers/template/listTemplates.ts b/src/helpers/template/listTemplates.ts new file mode 100644 index 0000000..e19861f --- /dev/null +++ b/src/helpers/template/listTemplates.ts @@ -0,0 +1,18 @@ +import type * as vscode from 'vscode'; +import type { AppError } from '../../types/AppError'; +import type { Result } from '../../types/Result'; +import { ok } from '../../types/Result'; +import { listWorkspaceEntries } from '../vscode/workspace/listEntries'; + +export const listTemplates = async ( + templatesUri: vscode.Uri, +): Promise> => { + const result = await listWorkspaceEntries(templatesUri); + if (!result.success) return result; + + const names = result.value + .filter(entry => entry.type === 'directory') + .map(entry => entry.name); + + return ok(names); +}; diff --git a/src/helpers/template/processTemplateFolder.ts b/src/helpers/template/processTemplateFolder.ts new file mode 100644 index 0000000..91017e7 --- /dev/null +++ b/src/helpers/template/processTemplateFolder.ts @@ -0,0 +1,60 @@ +import type * as vscode from 'vscode'; +import type { AppError } from '../../types/AppError'; +import { makeError } from '../../types/AppError'; +import type { Result } from '../../types/Result'; +import { err, ok } from '../../types/Result'; +import { readWorkspaceFile } from '../vscode/workspace/readFile'; +import { compileTemplate } from './compileTemplate'; +import { isTemplateFile } from './isTemplateFile'; +import { removeTemplateExtension } from './removeTemplateExtension'; +import { resolveTargetPath } from './resolveTargetPath'; +import { walkTemplateFolder } from './walkTemplateFolder'; + +export type ProcessedTemplateFile = { + readonly targetRelativePath: string; + readonly content: string; +}; + +export const processTemplateFolder = async ( + templateFolderUri: vscode.Uri, + templateName: string, + componentName: string, +): Promise> => { + const walkResult = await walkTemplateFolder(templateFolderUri); + if (!walkResult.success) return walkResult; + + const processed: ProcessedTemplateFile[] = []; + const seenPaths = new Set(); + + for (const entry of walkResult.value) { + if (!isTemplateFile(entry.relativePath)) continue; + + const contentResult = await readWorkspaceFile(entry.uri); + if (!contentResult.success) return contentResult; + + const compiledResult = compileTemplate(contentResult.value, { + name: componentName, + }); + if (!compiledResult.success) return compiledResult; + + const targetRelativePath = resolveTargetPath( + removeTemplateExtension(entry.relativePath), + templateName, + componentName, + ); + + if (seenPaths.has(targetRelativePath)) { + return err( + makeError( + 'TEMPLATE_DUPLICATE_PATH', + `Template produces duplicate output path: '${targetRelativePath}'.`, + ), + ); + } + seenPaths.add(targetRelativePath); + + processed.push({ targetRelativePath, content: compiledResult.value }); + } + + return ok(processed); +}; diff --git a/src/helpers/template/removeTemplateExtension.ts b/src/helpers/template/removeTemplateExtension.ts new file mode 100644 index 0000000..95ce34f --- /dev/null +++ b/src/helpers/template/removeTemplateExtension.ts @@ -0,0 +1,2 @@ +export const removeTemplateExtension = (filename: string): string => + filename.endsWith('.hbs') ? filename.slice(0, -4) : filename; diff --git a/src/helpers/template/renameTemplateFile.ts b/src/helpers/template/renameTemplateFile.ts new file mode 100644 index 0000000..6b0c5ed --- /dev/null +++ b/src/helpers/template/renameTemplateFile.ts @@ -0,0 +1,23 @@ +/** + * Renames a template filename by replacing the template folder name + * (or the literal "Template") with the component name. + * + * Examples (templateName = "ComponentSass", componentName = "Button"): + * "ComponentSass.tsx" → "Button.tsx" + * "ComponentSass.module.scss" → "Button.module.scss" + * "ComponentSass.spec.tsx" → "Button.spec.tsx" + * "index.tsx" → "index.tsx" (no match, unchanged) + */ +export const renameTemplateFile = ( + filename: string, + templateName: string, + componentName: string, +): string => { + if (filename.includes(templateName)) { + return filename.replace(templateName, componentName); + } + if (filename.includes('Template')) { + return filename.replace('Template', componentName); + } + return filename; +}; diff --git a/src/helpers/template/resolveTargetPath.ts b/src/helpers/template/resolveTargetPath.ts new file mode 100644 index 0000000..606acb9 --- /dev/null +++ b/src/helpers/template/resolveTargetPath.ts @@ -0,0 +1,31 @@ +import { renameTemplateFile } from './renameTemplateFile'; + +/** + * Resolves the output path for a generated file by applying naming rules + * to the filename portion only, preserving subdirectory structure. + * + * Examples (templateName = "ComponentSass", componentName = "Button"): + * "ComponentSass.tsx" → "Button.tsx" + * "index.tsx" → "index.tsx" + * "src/App.tsx" → "src/App.tsx" + * "src/lib/main.tsx" → "src/lib/main.tsx" + * + * Path traversal segments (`..`, `.`) are stripped defensively. + */ +export const resolveTargetPath = ( + relativePath: string, + templateName: string, + componentName: string, +): string => { + const segments = relativePath + .split('/') + .filter(s => s !== '..' && s !== '.' && s !== ''); + + const filename = segments[segments.length - 1] ?? ''; + const renamedFilename = renameTemplateFile( + filename, + templateName, + componentName, + ); + return [...segments.slice(0, -1), renamedFilename].join('/'); +}; diff --git a/src/helpers/template/walkTemplateFolder.ts b/src/helpers/template/walkTemplateFolder.ts new file mode 100644 index 0000000..fa23433 --- /dev/null +++ b/src/helpers/template/walkTemplateFolder.ts @@ -0,0 +1,52 @@ +import * as vscode from 'vscode'; +import type { AppError } from '../../types/AppError'; +import { makeError } from '../../types/AppError'; +import type { Result } from '../../types/Result'; +import { err, ok } from '../../types/Result'; +import { listWorkspaceEntries } from '../vscode/workspace/listEntries'; + +export type TemplateFileEntry = { + readonly relativePath: string; + readonly uri: vscode.Uri; +}; + +const MAX_DEPTH = 10; + +const walk = async ( + uri: vscode.Uri, + prefix: string, + depth: number, +): Promise> => { + if (depth > MAX_DEPTH) { + return err( + makeError( + 'TEMPLATE_MAX_DEPTH_EXCEEDED', + `Template folder exceeds the maximum allowed depth of ${MAX_DEPTH}.`, + ), + ); + } + + const result = await listWorkspaceEntries(uri); + if (!result.success) return result; + + const entries: TemplateFileEntry[] = []; + + for (const entry of result.value) { + const entryUri = vscode.Uri.joinPath(uri, entry.name); + const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name; + + if (entry.type === 'file') { + entries.push({ relativePath, uri: entryUri }); + } else if (entry.type === 'directory') { + const subResult = await walk(entryUri, relativePath, depth + 1); + if (!subResult.success) return subResult; + entries.push(...subResult.value); + } + } + + return ok(entries); +}; + +export const walkTemplateFolder = ( + uri: vscode.Uri, +): Promise> => walk(uri, '', 0); diff --git a/src/helpers/vscode/getWorkspaceFolders.ts b/src/helpers/vscode/getWorkspaceFolders.ts new file mode 100644 index 0000000..7c86ffe --- /dev/null +++ b/src/helpers/vscode/getWorkspaceFolders.ts @@ -0,0 +1,18 @@ +import * as vscode from 'vscode'; +import type { Option } from '../../types/Option'; + +export type WorkspaceFolder = { + readonly name: string; + readonly path: string; + readonly index: number; +}; + +export const getWorkspaceFolders = (): Option => { + const folders = vscode.workspace.workspaceFolders; + if (!folders || folders.length === 0) return undefined; + return folders.map(folder => ({ + name: folder.name, + path: folder.uri.fsPath, + index: folder.index, + })); +}; diff --git a/src/helpers/vscode/index.ts b/src/helpers/vscode/index.ts new file mode 100644 index 0000000..a268233 --- /dev/null +++ b/src/helpers/vscode/index.ts @@ -0,0 +1,16 @@ +export type { WorkspaceFolder } from './getWorkspaceFolders'; +export { getWorkspaceFolders } from './getWorkspaceFolders'; +export type { InputOptions } from './promptInput'; +export { promptInput } from './promptInput'; +export type { SelectionOptions } from './promptSelection'; +export { promptSelection } from './promptSelection'; +export { showError } from './showError'; +export { showInfo } from './showInfo'; +export type { DirectoryEntry } from './workspace'; +export { + createWorkspaceFolder, + listWorkspaceEntries, + readWorkspaceFile, + workspaceFileExists, + writeWorkspaceFile, +} from './workspace'; diff --git a/src/helpers/vscode/promptInput.ts b/src/helpers/vscode/promptInput.ts new file mode 100644 index 0000000..c4d14c8 --- /dev/null +++ b/src/helpers/vscode/promptInput.ts @@ -0,0 +1,21 @@ +import * as vscode from 'vscode'; +import type { Option } from '../../types/Option'; + +export type InputOptions = { + readonly prompt?: string; + readonly placeholder?: string; + readonly value?: string; + readonly validateInput?: (value: string) => string | undefined; +}; + +export const promptInput = ( + options: InputOptions = {}, +): Promise> => + Promise.resolve( + vscode.window.showInputBox({ + prompt: options.prompt, + placeHolder: options.placeholder, + value: options.value, + validateInput: options.validateInput, + }), + ); diff --git a/src/helpers/vscode/promptSelection.ts b/src/helpers/vscode/promptSelection.ts new file mode 100644 index 0000000..307fa07 --- /dev/null +++ b/src/helpers/vscode/promptSelection.ts @@ -0,0 +1,17 @@ +import * as vscode from 'vscode'; +import type { Option } from '../../types/Option'; + +export type SelectionOptions = { + readonly placeholder?: string; +}; + +export const promptSelection = ( + items: readonly string[], + options: SelectionOptions = {}, +): Promise> => + Promise.resolve( + vscode.window.showQuickPick([...items], { + placeHolder: options.placeholder, + canPickMany: false, + }), + ); diff --git a/src/helpers/vscode/showError.ts b/src/helpers/vscode/showError.ts new file mode 100644 index 0000000..f098b66 --- /dev/null +++ b/src/helpers/vscode/showError.ts @@ -0,0 +1,5 @@ +import * as vscode from 'vscode'; + +export const showError = (message: string): void => { + vscode.window.showErrorMessage(message); +}; diff --git a/src/helpers/vscode/showInfo.ts b/src/helpers/vscode/showInfo.ts new file mode 100644 index 0000000..4e1d29c --- /dev/null +++ b/src/helpers/vscode/showInfo.ts @@ -0,0 +1,5 @@ +import * as vscode from 'vscode'; + +export const showInfo = (message: string): void => { + vscode.window.showInformationMessage(message); +}; diff --git a/src/helpers/vscode/workspace/createFolder.ts b/src/helpers/vscode/workspace/createFolder.ts new file mode 100644 index 0000000..0d17c4f --- /dev/null +++ b/src/helpers/vscode/workspace/createFolder.ts @@ -0,0 +1,20 @@ +import * as vscode from 'vscode'; +import { type AppError, makeError } from '../../../types/AppError'; +import { err, ok, type Result } from '../../../types/Result'; + +export const createWorkspaceFolder = async ( + uri: vscode.Uri, +): Promise> => { + try { + await vscode.workspace.fs.createDirectory(uri); + return ok(undefined); + } catch (cause) { + return err( + makeError( + 'WORKSPACE_FOLDER_CREATE_ERROR', + `Failed to create workspace folder: ${uri.fsPath}`, + cause, + ), + ); + } +}; diff --git a/src/helpers/vscode/workspace/fileExists.ts b/src/helpers/vscode/workspace/fileExists.ts new file mode 100644 index 0000000..1d1fd10 --- /dev/null +++ b/src/helpers/vscode/workspace/fileExists.ts @@ -0,0 +1,12 @@ +import * as vscode from 'vscode'; + +export const workspaceFileExists = async ( + uri: vscode.Uri, +): Promise => { + try { + await vscode.workspace.fs.stat(uri); + return true; + } catch { + return false; + } +}; diff --git a/src/helpers/vscode/workspace/index.ts b/src/helpers/vscode/workspace/index.ts new file mode 100644 index 0000000..46dc5b5 --- /dev/null +++ b/src/helpers/vscode/workspace/index.ts @@ -0,0 +1,6 @@ +export { createWorkspaceFolder } from './createFolder'; +export { workspaceFileExists } from './fileExists'; +export type { DirectoryEntry } from './listEntries'; +export { listWorkspaceEntries } from './listEntries'; +export { readWorkspaceFile } from './readFile'; +export { writeWorkspaceFile } from './writeFile'; diff --git a/src/helpers/vscode/workspace/listEntries.ts b/src/helpers/vscode/workspace/listEntries.ts new file mode 100644 index 0000000..e253bb1 --- /dev/null +++ b/src/helpers/vscode/workspace/listEntries.ts @@ -0,0 +1,37 @@ +import * as vscode from 'vscode'; +import { type AppError, makeError } from '../../../types/AppError'; +import { err, ok, type Result } from '../../../types/Result'; + +export type DirectoryEntry = { + readonly name: string; + readonly type: 'file' | 'directory' | 'other'; +}; + +// vscode.FileType values: Unknown=0, File=1, Directory=2, SymbolicLink=64 +const toEntryType = (fileType: number): DirectoryEntry['type'] => { + if (fileType === 1) return 'file'; + if (fileType === 2) return 'directory'; + return 'other'; +}; + +export const listWorkspaceEntries = async ( + uri: vscode.Uri, +): Promise> => { + try { + const entries = await vscode.workspace.fs.readDirectory(uri); + return ok( + entries.map(([name, type]: [string, number]) => ({ + name, + type: toEntryType(type), + })), + ); + } catch (cause) { + return err( + makeError( + 'WORKSPACE_DIR_LIST_ERROR', + `Failed to list workspace directory: ${uri.fsPath}`, + cause, + ), + ); + } +}; diff --git a/src/helpers/vscode/workspace/readFile.ts b/src/helpers/vscode/workspace/readFile.ts new file mode 100644 index 0000000..0b244d0 --- /dev/null +++ b/src/helpers/vscode/workspace/readFile.ts @@ -0,0 +1,22 @@ +import * as vscode from 'vscode'; +import { type AppError, makeError } from '../../../types/AppError'; +import { err, ok, type Result } from '../../../types/Result'; + +const decoder = new TextDecoder(); + +export const readWorkspaceFile = async ( + uri: vscode.Uri, +): Promise> => { + try { + const bytes = await vscode.workspace.fs.readFile(uri); + return ok(decoder.decode(bytes)); + } catch (cause) { + return err( + makeError( + 'WORKSPACE_FILE_READ_ERROR', + `Failed to read workspace file: ${uri.fsPath}`, + cause, + ), + ); + } +}; diff --git a/src/helpers/vscode/workspace/writeFile.ts b/src/helpers/vscode/workspace/writeFile.ts new file mode 100644 index 0000000..88483f1 --- /dev/null +++ b/src/helpers/vscode/workspace/writeFile.ts @@ -0,0 +1,23 @@ +import * as vscode from 'vscode'; +import { type AppError, makeError } from '../../../types/AppError'; +import { err, ok, type Result } from '../../../types/Result'; + +const encoder = new TextEncoder(); + +export const writeWorkspaceFile = async ( + uri: vscode.Uri, + content: string, +): Promise> => { + try { + await vscode.workspace.fs.writeFile(uri, encoder.encode(content)); + return ok(undefined); + } catch (cause) { + return err( + makeError( + 'WORKSPACE_FILE_WRITE_ERROR', + `Failed to write workspace file: ${uri.fsPath}`, + cause, + ), + ); + } +}; diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts deleted file mode 100644 index 6c52384..0000000 --- a/src/interfaces/index.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { - CodebotError, - ConfigurationFile, - FileSystemEntry, - ProcessedTemplate, - ProjectContext, - TemplateStructure, - TemplateType, -} from '../types'; - -export interface IProjectDetector { - detectProject(folderPath: string): Promise; - isMultiProjectWorkspace(): boolean; - resolveProjectRoot(folderPath: string): string; -} - -export interface IConfigurationManager { - getConfiguration(projectPath: string): Promise; - resolveTemplatePath(projectPath: string): string; - getConfigurationPath(projectPath: string): string; -} - -export interface ITemplateManager { - discoverTemplates(projectPath: string): Promise; - processTemplate(templatePath: string, componentName: string): Promise; - getTemplateFiles(templateType: string): string[]; - scanTemplateStructure(templatePath: string): Promise; - processTemplateHierarchy( - templateStructure: TemplateStructure, - componentName: string, - ): Promise; - invalidateCache(): void; -} - -export interface IFileSystemManager { - createFile(path: string, content: string): Promise; - createFolder(path: string): Promise; - createFolderRecursive(path: string): Promise; - fileExists(path: string): Promise; - folderExists(path: string): Promise; - readFile(path: string): Promise; - listFiles(path: string): Promise; - listDirectories(path: string): Promise; - listFilesRecursive(path: string): Promise; - copyDirectoryStructure(sourcePath: string, targetPath: string): Promise; -} - -export interface IPathResolver { - resolveTemplatePath( - projectContext: ProjectContext, - templateName: string, - ): string; - resolveTargetPath( - projectContext: ProjectContext, - componentName: string, - selectedFolderPath?: string, - ): string; - validatePath(path: string): boolean; - sanitizePath(path: string): string; -} - -export interface IErrorHandler { - handleError(error: CodebotError): void; - createError(type: string, message: string, details?: unknown): CodebotError; - isRecoverable(error: CodebotError): boolean; -} - -export interface IUIManager { - getComponentName(): Promise; - selectTemplate(templates: TemplateType[]): Promise; - showMessage(message: string, type: 'info' | 'error' | 'warning'): void; -} diff --git a/src/managers/ConfigurationManager.ts b/src/managers/ConfigurationManager.ts deleted file mode 100644 index fab8126..0000000 --- a/src/managers/ConfigurationManager.ts +++ /dev/null @@ -1,218 +0,0 @@ -import * as path from 'node:path'; -import { - CONFIG_FILE_NAME, - DEFAULT_CONFIGURATION, - DEFAULT_TEMPLATE_FOLDER, -} from '../constants'; -import { CodebotError } from '../errors'; -import type { ConfigurationFile, IConfigurationManager } from '../types'; - -export class ConfigurationManager implements IConfigurationManager { - private configCache = new Map(); - - async getConfiguration(projectPath: string): Promise { - try { - // Check cache first - const cacheKey = this.normalizePath(projectPath); - if (this.configCache.has(cacheKey)) { - return this.configCache.get(cacheKey) as ConfigurationFile; - } - - // Try hierarchical configuration resolution - const config = await this.resolveHierarchicalConfiguration(projectPath); - - // Cache the result - this.configCache.set(cacheKey, config); - return config; - } catch (error) { - throw CodebotError.configurationError(projectPath, error as Error); - } - } - - resolveTemplatePath(projectPath: string): string { - const configPath = this.getConfigurationPath(projectPath); - const config = this.loadConfigurationSync(configPath); - - const templateFolder = - config?.templateFolderPath || DEFAULT_TEMPLATE_FOLDER; - return path.resolve(projectPath, templateFolder); - } - - getConfigurationPath(projectPath: string): string { - return path.join(projectPath, CONFIG_FILE_NAME); - } - - private async resolveHierarchicalConfiguration( - projectPath: string, - ): Promise { - const configurations: Partial[] = []; - - // 1. Try project-specific configuration - const projectConfig = await this.loadConfiguration(projectPath); - if (projectConfig) { - configurations.push(projectConfig); - } - - // 2. Try workspace root configuration (if different from project) - const workspaceRoot = this.findWorkspaceRoot(projectPath); - if (workspaceRoot && workspaceRoot !== projectPath) { - const workspaceConfig = await this.loadConfiguration(workspaceRoot); - if (workspaceConfig) { - configurations.push(workspaceConfig); - } - } - - // 3. Use default configuration as fallback - configurations.push(DEFAULT_CONFIGURATION); - - // Merge configurations (project takes precedence over workspace, workspace over default) - return this.mergeConfigurations(configurations); - } - - private async loadConfiguration( - configPath: string, - ): Promise { - try { - const fs = require('node:fs').promises; - const fullPath = path.join(configPath, CONFIG_FILE_NAME); - - const configContent = await fs.readFile(fullPath, 'utf8'); - return JSON.parse(configContent) as ConfigurationFile; - } catch (_error) { - // Configuration file doesn't exist or is invalid - return null; - } - } - - private loadConfigurationSync(configPath: string): ConfigurationFile | null { - try { - const fs = require('node:fs'); - const fullPath = path.join(configPath, CONFIG_FILE_NAME); - - const configContent = fs.readFileSync(fullPath, 'utf8'); - return JSON.parse(configContent) as ConfigurationFile; - } catch (_error) { - return null; - } - } - - private mergeConfigurations( - configurations: Partial[], - ): ConfigurationFile { - const merged: ConfigurationFile = { - templateFolderPath: DEFAULT_TEMPLATE_FOLDER, - multiProject: { - enabled: true, - projectDetection: 'auto', - }, - }; - - // Merge from least specific to most specific (reverse order) - for (const config of configurations.reverse()) { - if (config.templateFolderPath !== undefined) { - merged.templateFolderPath = config.templateFolderPath; - } - - if (config.multiProject) { - merged.multiProject = { - ...merged.multiProject, - ...config.multiProject, - }; - } - } - - return merged; - } - - private findWorkspaceRoot(projectPath: string): string | null { - // This is a simplified implementation - // In a real scenario, you'd use VS Code's workspace API - const vscode = require('vscode'); - const workspaceFolders = vscode.workspace.workspaceFolders; - - if (!workspaceFolders || workspaceFolders.length === 0) { - return null; - } - - // Find the workspace folder that contains the project path - for (const folder of workspaceFolders) { - const workspaceRoot = folder.uri.fsPath; - if (projectPath.startsWith(workspaceRoot)) { - return workspaceRoot; - } - } - - return workspaceFolders[0].uri.fsPath; - } - - private normalizePath(filePath: string): string { - return path.resolve(filePath).replace(/\\/g, '/'); - } - - // Method to clear cache when configurations change - clearCache(): void { - this.configCache.clear(); - } - - // Method to invalidate specific configuration - invalidateConfiguration(projectPath: string): void { - const cacheKey = this.normalizePath(projectPath); - this.configCache.delete(cacheKey); - } - - // Method to validate configuration - validateConfiguration(config: ConfigurationFile): boolean { - try { - // Basic validation - if ( - config.templateFolderPath && - typeof config.templateFolderPath !== 'string' - ) { - return false; - } - - if (config.multiProject) { - if (typeof config.multiProject.enabled !== 'boolean') { - return false; - } - - if ( - !['auto', 'manual'].includes(config.multiProject.projectDetection) - ) { - return false; - } - } - - return true; - } catch (_error) { - return false; - } - } - - // Method to create default configuration file - async createDefaultConfiguration(projectPath: string): Promise { - try { - const fs = require('node:fs').promises; - const configPath = this.getConfigurationPath(projectPath); - - const defaultConfig = { - templateFolderPath: DEFAULT_TEMPLATE_FOLDER, - multiProject: { - enabled: true, - projectDetection: 'auto', - }, - }; - - await fs.writeFile( - configPath, - JSON.stringify(defaultConfig, null, 2), - 'utf8', - ); - - // Invalidate cache for this path - this.invalidateConfiguration(projectPath); - } catch (error) { - throw CodebotError.configurationError(projectPath, error as Error); - } - } -} diff --git a/src/managers/FileSystemManager.ts b/src/managers/FileSystemManager.ts deleted file mode 100644 index 01a96a2..0000000 --- a/src/managers/FileSystemManager.ts +++ /dev/null @@ -1,265 +0,0 @@ -import * as fs from 'node:fs'; -import * as path from 'node:path'; -import type { IFileSystemManager } from '../interfaces'; -import { type CodebotError, ErrorType, type FileSystemEntry } from '../types'; - -export class FileSystemManager implements IFileSystemManager { - private static readonly MAX_DEPTH = 15; // Security limit for directory depth - - async createFile(filePath: string, content: string): Promise { - try { - // Ensure directory exists before creating file - const dir = path.dirname(filePath); - await this.createFolderRecursive(dir); - - fs.writeFileSync(filePath, content, { encoding: 'utf-8' }); - } catch (error) { - throw this.createFileSystemError( - `Failed to create file: ${filePath}`, - error, - ); - } - } - - async createFolder(folderPath: string): Promise { - try { - if (!fs.existsSync(folderPath)) { - fs.mkdirSync(folderPath); - } - } catch (error) { - throw this.createFileSystemError( - `Failed to create folder: ${folderPath}`, - error, - ); - } - } - - async createFolderRecursive(folderPath: string): Promise { - try { - if (!fs.existsSync(folderPath)) { - fs.mkdirSync(folderPath, { recursive: true }); - } - } catch (error) { - throw this.createFileSystemError( - `Failed to create folder recursively: ${folderPath}`, - error, - ); - } - } - - async fileExists(filePath: string): Promise { - try { - const stats = fs.statSync(filePath); - return stats.isFile(); - } catch { - return false; - } - } - - async folderExists(folderPath: string): Promise { - try { - const stats = fs.statSync(folderPath); - return stats.isDirectory(); - } catch { - return false; - } - } - - async readFile(filePath: string): Promise { - try { - return fs.readFileSync(filePath, 'utf8'); - } catch (error) { - throw this.createFileSystemError( - `Failed to read file: ${filePath}`, - error, - ); - } - } - - async listFiles(folderPath: string): Promise { - try { - if (!(await this.folderExists(folderPath))) { - return []; - } - - const entries = fs.readdirSync(folderPath); - const files: string[] = []; - - for (const entry of entries) { - const fullPath = path.join(folderPath, entry); - const stats = fs.statSync(fullPath); - if (stats.isFile()) { - files.push(entry); - } - } - - return files; - } catch (error) { - throw this.createFileSystemError( - `Failed to list files in: ${folderPath}`, - error, - ); - } - } - - async listDirectories(folderPath: string): Promise { - try { - if (!(await this.folderExists(folderPath))) { - return []; - } - - const entries = fs.readdirSync(folderPath); - const directories: string[] = []; - - for (const entry of entries) { - const fullPath = path.join(folderPath, entry); - const stats = fs.statSync(fullPath); - if (stats.isDirectory()) { - directories.push(entry); - } - } - - return directories; - } catch (error) { - throw this.createFileSystemError( - `Failed to list directories in: ${folderPath}`, - error, - ); - } - } - - async listFilesRecursive( - folderPath: string, - currentDepth = 0, - ): Promise { - if (currentDepth > FileSystemManager.MAX_DEPTH) { - throw this.createFileSystemError( - `Maximum directory depth exceeded: ${FileSystemManager.MAX_DEPTH}`, - null, - ); - } - - try { - if (!(await this.folderExists(folderPath))) { - return []; - } - - const entries: FileSystemEntry[] = []; - const items = fs.readdirSync(folderPath); - - for (const item of items) { - const fullPath = path.join(folderPath, item); - const stats = fs.statSync(fullPath); - const relativePath = path.relative(folderPath, fullPath); - - if (stats.isDirectory()) { - const children = await this.listFilesRecursive( - fullPath, - currentDepth + 1, - ); - entries.push({ - name: item, - path: fullPath, - relativePath, - isDirectory: true, - children, - }); - } else { - entries.push({ - name: item, - path: fullPath, - relativePath, - isDirectory: false, - }); - } - } - - return entries; - } catch (error) { - // Re-throw CodebotError instances (including depth errors) without wrapping - if ( - error && - typeof error === 'object' && - 'type' in error && - error.type === ErrorType.FILE_SYSTEM_ERROR - ) { - throw error; - } - throw this.createFileSystemError( - `Failed to list files recursively in: ${folderPath}`, - error, - ); - } - } - - async copyDirectoryStructure( - sourcePath: string, - targetPath: string, - ): Promise { - try { - if (!(await this.folderExists(sourcePath))) { - throw this.createFileSystemError( - `Source directory does not exist: ${sourcePath}`, - null, - ); - } - - await this.createFolderRecursive(targetPath); - - const entries = await this.listFilesRecursive(sourcePath); - await this.copyEntriesRecursive(entries, sourcePath, targetPath); - } catch (error) { - // Re-throw CodebotError instances without wrapping - if ( - error && - typeof error === 'object' && - 'type' in error && - error.type === ErrorType.FILE_SYSTEM_ERROR - ) { - throw error; - } - throw this.createFileSystemError( - `Failed to copy directory structure from ${sourcePath} to ${targetPath}`, - error, - ); - } - } - - private async copyEntriesRecursive( - entries: FileSystemEntry[], - sourcePath: string, - targetPath: string, - ): Promise { - for (const entry of entries) { - const sourceEntryPath = entry.path; - const relativeToSource = path.relative(sourcePath, sourceEntryPath); - const targetEntryPath = path.join(targetPath, relativeToSource); - - if (entry.isDirectory) { - await this.createFolderRecursive(targetEntryPath); - if (entry.children) { - await this.copyEntriesRecursive( - entry.children, - sourcePath, - targetPath, - ); - } - } else { - const content = await this.readFile(sourceEntryPath); - await this.createFile(targetEntryPath, content); - } - } - } - - private createFileSystemError( - message: string, - originalError: unknown, - ): CodebotError { - return { - type: ErrorType.FILE_SYSTEM_ERROR, - message, - details: originalError, - recoverable: false, - }; - } -} diff --git a/src/managers/ProjectDetector.ts b/src/managers/ProjectDetector.ts deleted file mode 100644 index 67f5a6b..0000000 --- a/src/managers/ProjectDetector.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { type Dirent, readdirSync } from 'node:fs'; -import * as path from 'node:path'; -import * as vscode from 'vscode'; -import { CONFIG_FILE_NAME, DEFAULT_TEMPLATE_FOLDER } from '../constants'; -import { CodebotError } from '../errors'; -import type { IProjectDetector, ProjectContext } from '../types'; - -export class ProjectDetector implements IProjectDetector { - private workspaceRoot: string; - private projectCache = new Map(); - - constructor() { - this.workspaceRoot = this.getWorkspaceRoot(); - } - - async detectProject(folderPath: string): Promise { - try { - // Check cache first - const cacheKey = this.normalizePath(folderPath); - if (this.projectCache.has(cacheKey)) { - return this.projectCache.get(cacheKey) as ProjectContext; - } - - const projectRoot = this.resolveProjectRoot(folderPath); - const isMultiProject = this.isMultiProjectWorkspace(); - - const context: ProjectContext = { - workspaceRoot: this.workspaceRoot, - projectRoot, - templatePath: this.resolveTemplatePath(projectRoot), - configPath: this.resolveConfigPath(projectRoot), - isMultiProject, - projectName: isMultiProject ? path.basename(projectRoot) : undefined, - }; - - // Cache the result - this.projectCache.set(cacheKey, context); - return context; - } catch (error) { - throw CodebotError.projectDetectionError(folderPath, error as Error); - } - } - - isMultiProjectWorkspace(): boolean { - if (!vscode.workspace.workspaceFolders) { - return false; - } - - // If there are multiple workspace folders, it's definitely multi-project - if (vscode.workspace.workspaceFolders.length > 1) { - return true; - } - - // Check if the single workspace contains multiple project-like folders - return this.detectSubProjects(); - } - - resolveProjectRoot(folderPath: string): string { - const normalizedPath = this.normalizePath(folderPath); - - // If it's a multi-project workspace, find the nearest project root - if (this.isMultiProjectWorkspace()) { - return this.findNearestProjectRoot(normalizedPath); - } - - // For single project, the workspace root is the project root - return this.workspaceRoot; - } - - private getWorkspaceRoot(): string { - const workspaceFolders = vscode.workspace.workspaceFolders; - if (!workspaceFolders || workspaceFolders.length === 0) { - throw CodebotError.workspaceNotFound(); - } - return workspaceFolders[0].uri.fsPath; - } - - private normalizePath(filePath: string): string { - return path.resolve(filePath).replace(/\\/g, '/'); - } - - private resolveTemplatePath(projectRoot: string): string { - return path.join(projectRoot, DEFAULT_TEMPLATE_FOLDER); - } - - private resolveConfigPath(projectRoot: string): string { - return path.join(projectRoot, CONFIG_FILE_NAME); - } - - private detectSubProjects(): boolean { - try { - const workspaceContents = readdirSync(this.workspaceRoot, { - withFileTypes: true, - }); - - // Look for multiple directories that could be projects - const potentialProjects = workspaceContents - .filter((dirent: Dirent) => dirent.isDirectory()) - .filter((dirent: Dirent) => !dirent.name.startsWith('.')) - .filter((dirent: Dirent) => - this.looksLikeProject(path.join(this.workspaceRoot, dirent.name)), - ); - - return potentialProjects.length > 1; - } catch (_error) { - // If we can't read the workspace, assume single project - return false; - } - } - - private looksLikeProject(dirPath: string): boolean { - try { - const contents = readdirSync(dirPath); - - // Check for common project indicators - const projectIndicators = [ - 'package.json', - 'tsconfig.json', - 'src', - 'lib', - 'templates', - CONFIG_FILE_NAME, - ]; - - return projectIndicators.some(indicator => contents.includes(indicator)); - } catch (_error) { - return false; - } - } - - private findNearestProjectRoot(folderPath: string): string { - let currentPath = folderPath; - - // Walk up the directory tree until we find a project root or reach workspace root - while ( - currentPath !== this.workspaceRoot && - currentPath !== path.dirname(currentPath) - ) { - if (this.looksLikeProject(currentPath)) { - return currentPath; - } - currentPath = path.dirname(currentPath); - } - - // If we reach workspace root and it looks like a project, use it - if (this.looksLikeProject(this.workspaceRoot)) { - return this.workspaceRoot; - } - - // Otherwise, try to find the nearest sibling project - const parentDir = path.dirname(folderPath); - if (parentDir !== this.workspaceRoot) { - return this.findNearestProjectRoot(parentDir); - } - - // Fallback to workspace root - return this.workspaceRoot; - } - - // Method to clear cache when workspace changes - clearCache(): void { - this.projectCache.clear(); - } - - // Method to get all detected projects - getAllProjects(): ProjectContext[] { - return Array.from(this.projectCache.values()); - } -} diff --git a/src/managers/TemplateManager.ts b/src/managers/TemplateManager.ts deleted file mode 100644 index 1e109ee..0000000 --- a/src/managers/TemplateManager.ts +++ /dev/null @@ -1,690 +0,0 @@ -import * as path from 'node:path'; -import * as handlebars from 'handlebars'; -import { DEFAULT_TEMPLATE_FOLDER, TEMPLATE_FILE_EXTENSION } from '../constants'; -import { CodebotError } from '../errors'; -import type { - ITemplateManager, - ProcessedDirectory, - ProcessedFile, - ProcessedTemplate, - TemplateDirectoryInfo, - TemplateFileInfo, - TemplateStructure, - TemplateType, -} from '../types'; -import { DirectoryProcessor } from '../utils/DirectoryProcessor'; -import { isTemplateFile } from '../utils/validation'; -import { FileSystemManager } from './FileSystemManager'; - -interface TemplateCache { - templates: TemplateType[]; - timestamp: number; - projectPath: string; -} - -interface CompiledTemplateCache { - compiled: HandlebarsTemplateDelegate; - timestamp: number; -} - -export class TemplateManager implements ITemplateManager { - private templateCache = new Map(); - private compiledCache = new Map(); - private readonly CACHE_TTL = 5 * 60 * 1000; // 5 minutes - private directoryProcessor: DirectoryProcessor; - private fileSystemManager: FileSystemManager; - - constructor(fileSystemManager?: FileSystemManager) { - this.fileSystemManager = fileSystemManager || new FileSystemManager(); - this.directoryProcessor = new DirectoryProcessor(this.fileSystemManager); - } - - async discoverTemplates(projectPath: string): Promise { - try { - // Check cache first - const cacheKey = this.normalizePath(projectPath); - const cached = this.templateCache.get(cacheKey); - - if (cached && this.isCacheValid(cached.timestamp)) { - return cached.templates; - } - - // Discover templates hierarchically - const templates = await this.discoverTemplatesHierarchically(projectPath); - - // Cache the results - this.templateCache.set(cacheKey, { - templates, - timestamp: Date.now(), - projectPath, - }); - - return templates; - } catch (error) { - throw CodebotError.templateProcessingError(projectPath, error as Error); - } - } - - async processTemplate( - templatePath: string, - componentName: string, - ): Promise { - try { - // Check compiled cache first (cache by template path only, since compiled template is the same regardless of component name) - const cached = this.compiledCache.get(templatePath); - - if (cached && this.isCacheValid(cached.timestamp)) { - return cached.compiled({ - name: this.formatComponentName(componentName), - }); - } - - // Read and compile template - const templateContent = await this.readTemplateFile(templatePath); - const compiled = handlebars.compile(templateContent); - - // Cache compiled template - this.compiledCache.set(templatePath, { - compiled, - timestamp: Date.now(), - }); - - return compiled({ name: this.formatComponentName(componentName) }); - } catch (error) { - throw CodebotError.templateProcessingError(templatePath, error as Error); - } - } - - getTemplateFiles(templateType: string): string[] { - try { - const fs = require('node:fs'); - const files = fs.readdirSync(templateType); - - return files.filter((file: string) => isTemplateFile(file)); - } catch (error) { - throw CodebotError.templateProcessingError(templateType, error as Error); - } - } - - async scanTemplateStructure( - templatePath: string, - ): Promise { - try { - const structure = - await this.directoryProcessor.scanTemplateStructure(templatePath); - - // Validate depth for security - this.directoryProcessor.validateDepth(structure); - - return structure; - } catch (error) { - throw CodebotError.templateProcessingError(templatePath, error as Error); - } - } - - async processTemplateHierarchy( - templateStructure: TemplateStructure, - componentName: string, - ): Promise { - try { - // Extract template folder name from the root path - const templateFolderName = this.extractTemplateFolderName( - templateStructure.rootPath, - ); - - const processedFiles = await this.processFiles( - templateStructure.files, - componentName, - '', - templateFolderName, - ); - const processedDirectories = await this.processDirectories( - templateStructure.directories, - componentName, - '', - templateFolderName, - ); - - const processedTemplate = { - componentName, - files: processedFiles, - directories: processedDirectories, - }; - - // Validate the processed template for integrity - this.validateProcessedTemplate(processedTemplate); - - return processedTemplate; - } catch (error) { - throw CodebotError.templateProcessingError( - templateStructure.rootPath, - error as Error, - ); - } - } - - invalidateCache(): void { - this.templateCache.clear(); - this.compiledCache.clear(); - } - - getCacheStats(): { templateCacheSize: number; compiledCacheSize: number } { - return { - templateCacheSize: this.templateCache.size, - compiledCacheSize: this.compiledCache.size, - }; - } - - invalidateProjectCache(projectPath: string): void { - const cacheKey = this.normalizePath(projectPath); - this.templateCache.delete(cacheKey); - } - - cleanExpiredCache(): void { - // Clean template cache - for (const [key, cache] of this.templateCache.entries()) { - if (!this.isCacheValid(cache.timestamp)) { - this.templateCache.delete(key); - } - } - - // Clean compiled cache - for (const [key, cache] of this.compiledCache.entries()) { - if (!this.isCacheValid(cache.timestamp)) { - this.compiledCache.delete(key); - } - } - } - - async preloadTemplates(templatePaths: string[]): Promise { - for (const templatePath of templatePaths) { - try { - await this.discoverTemplates(templatePath); - } catch (error) { - throw CodebotError.templateProcessingError( - templatePath, - error as Error, - ); - } - } - } - - private async discoverTemplatesHierarchically( - projectPath: string, - ): Promise { - const templatePaths = this.getTemplatePaths(projectPath); - const allTemplates: TemplateType[] = []; - const seenTemplates = new Set(); - - // Search in order of priority: project -> workspace -> default - for (const templatePath of templatePaths) { - const templates = await this.discoverTemplatesInPath(templatePath); - - // Add only unique templates (by name) - for (const template of templates) { - if (!seenTemplates.has(template.name)) { - allTemplates.push(template); - seenTemplates.add(template.name); - } - } - } - - return allTemplates; - } - - private getTemplatePaths(projectPath: string): string[] { - const paths: string[] = []; - - // 1. Project-specific templates - const projectTemplatePath = path.join(projectPath, DEFAULT_TEMPLATE_FOLDER); - paths.push(projectTemplatePath); - - // 2. Workspace root templates (if different from project) - const workspaceRoot = this.findWorkspaceRoot(projectPath); - if (workspaceRoot && workspaceRoot !== projectPath) { - const workspaceTemplatePath = path.join( - workspaceRoot, - DEFAULT_TEMPLATE_FOLDER, - ); - paths.push(workspaceTemplatePath); - } - - return paths; - } - - private async discoverTemplatesInPath( - templatePath: string, - ): Promise { - try { - // Check if template directory exists - if (!(await this.fileSystemManager.folderExists(templatePath))) { - return []; // Directory doesn't exist - } - - const entries = - await this.fileSystemManager.listDirectories(templatePath); - const templates: TemplateType[] = []; - - for (const entry of entries) { - const templateTypePath = path.join(templatePath, entry); - const files = await this.getTemplateFilesInDirectory(templateTypePath); - const structure = await this.scanTemplateStructure(templateTypePath); - const hasSubdirectories = structure.directories.length > 0; - - if (files.length > 0 || hasSubdirectories) { - templates.push({ - name: entry, - path: templateTypePath, - files, - hasSubdirectories, - structure, - }); - } - } - - return templates; - } catch (_error) { - // If we can't read the directory, return empty array - return []; - } - } - - private async getTemplateFilesInDirectory( - directoryPath: string, - ): Promise { - try { - const files = await this.fileSystemManager.listFiles(directoryPath); - return files.filter((file: string) => isTemplateFile(file)); - } catch (_error) { - return []; - } - } - - private async readTemplateFile(templatePath: string): Promise { - try { - return await this.fileSystemManager.readFile(templatePath); - } catch (_error) { - throw new Error(`Failed to read template file: ${templatePath}`); - } - } - - private formatComponentName(componentName: string): string { - // Convert to PascalCase, preserving existing case if already in PascalCase - if (/^[A-Z][a-zA-Z0-9]*$/.test(componentName)) { - return componentName; // Already in PascalCase - } - - return componentName - .split(/[-_\s]+/) - .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) - .join(''); - } - - private findWorkspaceRoot(projectPath: string): string | null { - try { - const vscode = require('vscode'); - const workspaceFolders = vscode.workspace.workspaceFolders; - - if (!workspaceFolders || workspaceFolders.length === 0) { - return null; - } - - // Find the workspace folder that contains the project path - for (const folder of workspaceFolders) { - const workspaceRoot = folder.uri.fsPath; - if (projectPath.startsWith(workspaceRoot)) { - return workspaceRoot; - } - } - - return workspaceFolders[0].uri.fsPath; - } catch (_error) { - return null; - } - } - - private normalizePath(filePath: string): string { - return path.resolve(filePath).replace(/\\/g, '/'); - } - - private extractTemplateFolderName( - templateRootPath: string, - ): string | undefined { - try { - if (!templateRootPath || typeof templateRootPath !== 'string') { - return undefined; - } - - // Handle both Unix and Windows path separators - const normalizedPath = templateRootPath.replace(/\\/g, '/'); - const pathParts = normalizedPath - .split('/') - .filter(part => part.length > 0); - - if (pathParts.length === 0) { - return undefined; - } - - const folderName = pathParts[pathParts.length - 1]; - - // Return undefined if we can't determine a valid folder name - if (!folderName || folderName === '.' || folderName === '..') { - return undefined; - } - - return folderName; - } catch (_error) { - // If there's any error extracting the folder name, return undefined - return undefined; - } - } - - private isCacheValid(timestamp: number): boolean { - return Date.now() - timestamp < this.CACHE_TTL; - } - - private async processFiles( - files: TemplateFileInfo[], - componentName: string, - basePath = '', - templateFolderName?: string, - ): Promise { - const processedFiles: ProcessedFile[] = []; - - for (const file of files) { - if (isTemplateFile(file.name)) { - const content = await this.fileSystemManager.readFile(file.path); - const processedContent = this.processTemplateContent( - content, - componentName, - ); - const targetPath = this.generateTargetPath( - file.relativePath, - componentName, - basePath, - templateFolderName, - ); - - processedFiles.push({ - originalPath: file.path, - targetPath, - content: processedContent, - }); - } - } - - return processedFiles; - } - - private async processDirectories( - directories: TemplateDirectoryInfo[], - componentName: string, - basePath = '', - templateFolderName?: string, - ): Promise { - const processedDirectories: ProcessedDirectory[] = []; - - for (const directory of directories) { - // Build the accumulated path for this directory - const currentDirectoryPath = basePath - ? path.join(basePath, directory.relativePath) - : directory.relativePath; - - // Process files and subdirectories with the accumulated path - const processedFiles = await this.processFiles( - directory.files, - componentName, - currentDirectoryPath, - templateFolderName, - ); - const processedSubdirectories = await this.processDirectories( - directory.subdirectories, - componentName, - currentDirectoryPath, - templateFolderName, - ); - const targetPath = this.generateTargetPath( - directory.relativePath, - componentName, - basePath, - templateFolderName, - ); - - processedDirectories.push({ - originalPath: directory.path, - targetPath, - files: processedFiles, - subdirectories: processedSubdirectories, - }); - } - - return processedDirectories; - } - - private processTemplateContent( - content: string, - componentName: string, - ): string { - try { - const compiled = handlebars.compile(content); - return compiled({ name: this.formatComponentName(componentName) }); - } catch (error) { - throw new Error(`Failed to process template content: ${error}`); - } - } - - private generateTargetPath( - relativePath: string, - componentName: string, - basePath = '', - templateFolderName?: string, - ): string { - // Validate the input relativePath first to prevent path traversal attacks - this.validateTargetPath(relativePath); - - // Validate basePath if provided - if (basePath) { - this.validateTargetPath(basePath); - } - - // Remove .hbs extension and apply naming rules - let targetPath = relativePath; - - if (targetPath.endsWith(TEMPLATE_FILE_EXTENSION)) { - targetPath = targetPath.slice(0, -TEMPLATE_FILE_EXTENSION.length); - } - - // Apply component naming rules to the path - const processedPath = this.applyNamingRules( - targetPath, - componentName, - templateFolderName, - ); - - // Combine basePath with processedPath using path.join for cross-platform compatibility - // path.join handles empty strings gracefully and ensures proper path separators - const finalPath = basePath - ? path.join(basePath, processedPath) - : processedPath; - - // Validate the final path as well to ensure no issues after processing - this.validateTargetPath(finalPath); - - return finalPath; - } - - private applyNamingRules( - filePath: string, - componentName: string, - templateFolderName?: string, - ): string { - const pathParts = filePath.split(path.sep); - const fileName = pathParts[pathParts.length - 1]; - let newFileName = fileName; - - // First, check if the filename starts with the template folder name (case-insensitive) - if ( - templateFolderName && - fileName.toLowerCase().startsWith(templateFolderName.toLowerCase()) - ) { - // Replace the matched portion with the component name, preserving the case of the component name - const _matchedPortion = fileName.substring(0, templateFolderName.length); - const remainingPortion = fileName.substring(templateFolderName.length); - newFileName = this.formatComponentName(componentName) + remainingPortion; - } - - // Then, apply existing template/Template replacement functionality - if (newFileName.includes('template') || newFileName.includes('Template')) { - newFileName = newFileName - .replace(/template/gi, componentName.toLowerCase()) - .replace(/Template/g, this.formatComponentName(componentName)); - } - - pathParts[pathParts.length - 1] = newFileName; - return pathParts.join(path.sep); - } - - private validateTargetPath(targetPath: string): void { - // Skip validation for empty strings - if (!targetPath) { - return; - } - - // Prevent path traversal attacks - check for .. sequences - if (targetPath.includes('..')) { - throw new Error( - `Invalid target path - contains path traversal: ${targetPath}`, - ); - } - - // Prevent absolute paths - if (path.isAbsolute(targetPath)) { - throw new Error( - `Invalid target path - absolute paths not allowed: ${targetPath}`, - ); - } - - // Check for Windows and Unix absolute path patterns explicitly - if (targetPath.startsWith('/') || /^[a-zA-Z]:/.test(targetPath)) { - throw new Error( - `Invalid target path - absolute path detected: ${targetPath}`, - ); - } - - // Normalize the path and check for any remaining path traversal attempts - const normalizedPath = path.normalize(targetPath); - - // After normalization, check again for path traversal and absolute paths - if (normalizedPath.includes('..')) { - throw new Error( - `Invalid target path - path traversal detected after normalization: ${normalizedPath}`, - ); - } - - if (path.isAbsolute(normalizedPath)) { - throw new Error( - `Invalid target path - absolute path detected after normalization: ${normalizedPath}`, - ); - } - - // Additional check for paths that start with path separators after normalization - if (normalizedPath.startsWith('/') || normalizedPath.startsWith('\\')) { - throw new Error( - `Invalid target path - starts with path separator after normalization: ${normalizedPath}`, - ); - } - - // Check for Windows drive letters after normalization - if (/^[a-zA-Z]:/.test(normalizedPath)) { - throw new Error( - `Invalid target path - Windows drive letter detected after normalization: ${normalizedPath}`, - ); - } - } - - /** - * Validates the processed template for integrity issues like duplicate target paths - * @param processedTemplate The processed template to validate - * @throws Error if validation fails - */ - private validateProcessedTemplate( - processedTemplate: ProcessedTemplate, - ): void { - const allTargetPaths = this.collectAllTargetPaths(processedTemplate); - const duplicates = this.findDuplicatePaths(allTargetPaths); - - if (duplicates.length > 0) { - throw new Error( - `Duplicate target paths detected: ${duplicates.join(', ')}`, - ); - } - } - - /** - * Collects all target paths from a processed template, including files in nested directories - * @param processedTemplate The processed template to collect paths from - * @returns Array of all target paths - */ - private collectAllTargetPaths( - processedTemplate: ProcessedTemplate, - ): string[] { - const allPaths: string[] = []; - - // Collect paths from root-level files - for (const file of processedTemplate.files) { - allPaths.push(file.targetPath); - } - - // Recursively collect paths from directories - this.collectPathsFromDirectories(processedTemplate.directories, allPaths); - - return allPaths; - } - - /** - * Recursively collects target paths from directories and their subdirectories - * @param directories Array of processed directories to collect paths from - * @param allPaths Array to accumulate all paths - */ - private collectPathsFromDirectories( - directories: ProcessedDirectory[], - allPaths: string[], - ): void { - for (const directory of directories) { - // Collect paths from files in this directory - for (const file of directory.files) { - allPaths.push(file.targetPath); - } - - // Recursively collect paths from subdirectories - if (directory.subdirectories.length > 0) { - this.collectPathsFromDirectories(directory.subdirectories, allPaths); - } - } - } - - /** - * Finds duplicate paths in an array of target paths - * @param targetPaths Array of target paths to check for duplicates - * @returns Array of duplicate paths - */ - private findDuplicatePaths(targetPaths: string[]): string[] { - const pathCounts = new Map(); - const duplicates: string[] = []; - - // Count occurrences of each path - for (const path of targetPaths) { - const count = pathCounts.get(path) || 0; - pathCounts.set(path, count + 1); - } - - // Find paths that appear more than once - for (const [path, count] of pathCounts.entries()) { - if (count > 1) { - duplicates.push(path); - } - } - - return duplicates; - } -} diff --git a/src/managers/index.ts b/src/managers/index.ts deleted file mode 100644 index 9b9a822..0000000 --- a/src/managers/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './ConfigurationManager'; -export * from './FileSystemManager'; -export * from './ProjectDetector'; -export * from './TemplateManager'; diff --git a/src/test/__mocks__/vscode.js b/src/test/__mocks__/vscode.js deleted file mode 100644 index aab0228..0000000 --- a/src/test/__mocks__/vscode.js +++ /dev/null @@ -1,51 +0,0 @@ -// Mock do módulo vscode para testes -const vscode = { - workspace: { - workspaceFolders: [ - { - uri: { fsPath: '/test/workspace' }, - name: 'test-workspace', - index: 0, - }, - ], - getConfiguration: jest.fn(() => ({ - get: jest.fn(), - update: jest.fn(), - has: jest.fn(() => true), - inspect: jest.fn(), - })), - onDidChangeConfiguration: jest.fn(), - findFiles: jest.fn(() => Promise.resolve([])), - openTextDocument: jest.fn(), - saveAll: jest.fn(), - }, - window: { - showInformationMessage: jest.fn(), - showErrorMessage: jest.fn(), - showWarningMessage: jest.fn(), - showInputBox: jest.fn(), - showQuickPick: jest.fn(), - createOutputChannel: jest.fn(() => ({ - appendLine: jest.fn(), - show: jest.fn(), - hide: jest.fn(), - dispose: jest.fn(), - })), - }, - Uri: { - file: jest.fn(path => ({ fsPath: path, scheme: 'file', path })), - parse: jest.fn(uri => ({ fsPath: uri, scheme: 'file', path: uri })), - }, - commands: { - registerCommand: jest.fn(), - executeCommand: jest.fn(), - }, - ExtensionContext: jest.fn(), - ConfigurationTarget: { - Global: 1, - Workspace: 2, - WorkspaceFolder: 3, - }, -}; - -module.exports = vscode; diff --git a/src/test/commands/updateComponent.test.ts b/src/test/commands/updateComponent.test.ts deleted file mode 100644 index 8f510ba..0000000 --- a/src/test/commands/updateComponent.test.ts +++ /dev/null @@ -1,288 +0,0 @@ -import * as fs from 'node:fs'; -import * as os from 'node:os'; -import * as path from 'node:path'; -import { updateComponent } from '../../commands/updateComponent'; -import { FileSystemManager } from '../../managers/FileSystemManager'; -import type { CommandArgs } from '../../types'; - -// Mock VS Code API -jest.mock('vscode', () => ({ - workspace: { - workspaceFolders: [ - { - uri: { fsPath: '/test/workspace' }, - }, - ], - }, - window: { - showInputBox: jest.fn(), - showQuickPick: jest.fn(), - showInformationMessage: jest.fn(), - showErrorMessage: jest.fn(), - }, -})); - -// Mock helpers -jest.mock('../../helpers', () => ({ - showMessage: jest.fn(), - showSearchDropdown: jest.fn(), - createFolder: jest.fn(), - createFile: jest.fn(), - checkExistsFile: jest.fn(), - formatTemplateName: jest.fn((templateFile, templateName, componentName) => { - return templateFile - .replace('.hbs', '') - .replace(templateName, componentName); - }), -})); - -describe('updateComponent with hierarchical support', () => { - let tempDir: string; - let fileSystemManager: FileSystemManager; - let mockHelpers: jest.Mocked; - - beforeEach(() => { - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codebot-update-test-')); - fileSystemManager = new FileSystemManager(); - - // Get mocked helpers - mockHelpers = require('../../helpers'); - - // Reset all mocks - jest.clearAllMocks(); - }); - - afterEach(() => { - if (fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true, force: true }); - } - }); - - describe('hierarchical template processing', () => { - beforeEach(async () => { - // Create a complex template structure - const templateStructure = { - 'component.hbs': 'export const {{name}} = () => {};', - 'component.test.hbs': 'describe("{{name}}", () => {});', - 'styles/component.css.hbs': '.{{name}} { color: red; }', - 'styles/themes/light.css.hbs': '.{{name}}.light { background: white; }', - 'utils/helper.js.hbs': 'export const {{name}}Helper = () => {};', - }; - - const templatesDir = path.join(tempDir, 'templates', 'react-component'); - await fileSystemManager.createFolderRecursive(templatesDir); - - for (const [filePath, content] of Object.entries(templateStructure)) { - const fullPath = path.join(templatesDir, filePath); - await fileSystemManager.createFile(fullPath, content); - } - - // Create component directory - const componentDir = path.join(tempDir, 'UserProfile'); - await fileSystemManager.createFolderRecursive(componentDir); - }); - - it('should create missing files in hierarchical structure', async () => { - const componentDir = path.join(tempDir, 'UserProfile'); - - // Mock helpers to simulate user interactions - mockHelpers.showSearchDropdown.mockResolvedValue('react-component'); - mockHelpers.checkExistsFile.mockImplementation((filePath: string) => { - return fs.existsSync(filePath); - }); - mockHelpers.createFolder.mockImplementation((folderPath: string) => { - fs.mkdirSync(folderPath, { recursive: true }); - }); - mockHelpers.createFile.mockImplementation( - (filePath: string, content: string) => { - fs.mkdirSync(path.dirname(filePath), { recursive: true }); - fs.writeFileSync(filePath, content); - }, - ); - - const args: CommandArgs = { fsPath: componentDir }; - - await updateComponent(args); - - // Verify error message was shown (no templates found) - expect(mockHelpers.showMessage).toHaveBeenCalledWith( - expect.stringContaining('No templates found in project'), - 'error', - ); - }); - - it('should skip existing files and only create missing ones', async () => { - const componentDir = path.join(tempDir, 'UserProfile'); - - // Pre-create some files to simulate existing component - const existingFiles = [ - 'UserProfile.js', // component.hbs -> UserProfile.js - 'styles/UserProfile.css', // styles/component.css.hbs -> styles/UserProfile.css - ]; - - for (const file of existingFiles) { - const filePath = path.join(componentDir, file); - await fileSystemManager.createFile(filePath, 'existing content'); - } - - // Mock helpers - mockHelpers.showSearchDropdown.mockResolvedValue('react-component'); - mockHelpers.checkExistsFile.mockImplementation((filePath: string) => { - return fs.existsSync(filePath); - }); - mockHelpers.createFolder.mockImplementation((folderPath: string) => { - if (!fs.existsSync(folderPath)) { - fs.mkdirSync(folderPath, { recursive: true }); - } - }); - mockHelpers.createFile.mockImplementation( - (filePath: string, content: string) => { - if (!fs.existsSync(filePath)) { - fs.mkdirSync(path.dirname(filePath), { recursive: true }); - fs.writeFileSync(filePath, content); - } - }, - ); - - const args: CommandArgs = { fsPath: componentDir }; - - await updateComponent(args); - - // Verify error message was shown (no templates found) - expect(mockHelpers.showMessage).toHaveBeenCalledWith( - expect.stringContaining('No templates found in project'), - 'error', - ); - }); - - it('should handle templates with only subdirectories', async () => { - // Create a template with only subdirectory files - const templateDir = path.join(tempDir, 'templates', 'styles-only'); - const templateStructure = { - 'styles/main.css.hbs': '.{{name}} { display: block; }', - 'styles/themes/dark.css.hbs': '.{{name}}.dark { color: white; }', - 'assets/icons/icon.svg.hbs': '{{name}}', - }; - - await fileSystemManager.createFolderRecursive(templateDir); - for (const [filePath, content] of Object.entries(templateStructure)) { - const fullPath = path.join(templateDir, filePath); - await fileSystemManager.createFile(fullPath, content); - } - - const componentDir = path.join(tempDir, 'StyleComponent'); - await fileSystemManager.createFolderRecursive(componentDir); - - // Mock helpers - mockHelpers.showSearchDropdown.mockResolvedValue('styles-only'); - mockHelpers.checkExistsFile.mockImplementation((filePath: string) => { - return fs.existsSync(filePath); - }); - mockHelpers.createFolder.mockImplementation((folderPath: string) => { - fs.mkdirSync(folderPath, { recursive: true }); - }); - mockHelpers.createFile.mockImplementation( - (filePath: string, content: string) => { - fs.mkdirSync(path.dirname(filePath), { recursive: true }); - fs.writeFileSync(filePath, content); - }, - ); - - const args: CommandArgs = { fsPath: componentDir }; - - await updateComponent(args); - - // Should show error message (no templates found) - expect(mockHelpers.showMessage).toHaveBeenCalledWith( - expect.stringContaining('No templates found in project'), - 'error', - ); - }); - - it('should report when component is already up to date', async () => { - const componentDir = path.join(tempDir, 'ExistingComponent'); - - // Pre-create all template files - const allFiles = [ - 'ExistingComponent.js', - 'ExistingComponent.test.js', - 'styles/ExistingComponent.css', - 'styles/themes/light.css', - 'utils/helper.js', - ]; - - for (const file of allFiles) { - const filePath = path.join(componentDir, file); - await fileSystemManager.createFile(filePath, 'existing content'); - } - - // Mock helpers - mockHelpers.showSearchDropdown.mockResolvedValue('react-component'); - mockHelpers.checkExistsFile.mockImplementation((filePath: string) => { - return fs.existsSync(filePath); - }); - - const args: CommandArgs = { fsPath: componentDir }; - - await updateComponent(args); - - // Should show error message (no templates found) - expect(mockHelpers.showMessage).toHaveBeenCalledWith( - expect.stringContaining('No templates found in project'), - 'error', - ); - }); - }); - - describe('error handling', () => { - it('should handle empty template structures', async () => { - // Create empty template directory - const emptyTemplateDir = path.join( - tempDir, - 'templates', - 'empty-template', - ); - await fileSystemManager.createFolderRecursive(emptyTemplateDir); - - const componentDir = path.join(tempDir, 'TestComponent'); - await fileSystemManager.createFolderRecursive(componentDir); - - mockHelpers.showSearchDropdown.mockResolvedValue('empty-template'); - mockHelpers.checkExistsFile.mockImplementation((filePath: string) => { - return fs.existsSync(filePath); - }); - - const args: CommandArgs = { fsPath: componentDir }; - - await updateComponent(args); - - // Should show error for no templates found - expect(mockHelpers.showMessage).toHaveBeenCalledWith( - expect.stringContaining('No templates found in project'), - 'error', - ); - }); - - it('should handle file creation errors gracefully', async () => { - const componentDir = path.join(tempDir, 'ErrorComponent'); - await fileSystemManager.createFolderRecursive(componentDir); - - // Mock helpers - mockHelpers.showSearchDropdown.mockResolvedValue('react-component'); - mockHelpers.checkExistsFile.mockReturnValue(false); // No files exist - mockHelpers.createFile.mockImplementation(() => { - throw new Error('Permission denied'); - }); - - const args: CommandArgs = { fsPath: componentDir }; - - await updateComponent(args); - - // Should show error message (no templates found) - expect(mockHelpers.showMessage).toHaveBeenCalledWith( - expect.stringContaining('No templates found in project'), - 'error', - ); - }); - }); -}); diff --git a/src/test/fixtures/index.ts b/src/test/fixtures/index.ts deleted file mode 100644 index 0318d4e..0000000 --- a/src/test/fixtures/index.ts +++ /dev/null @@ -1,61 +0,0 @@ -import type { - ConfigurationFile, - ProjectContext, - TemplateType, -} from '../../types'; - -export const mockConfiguration: ConfigurationFile = { - templateFolderPath: 'templates', - multiProject: { - enabled: true, - projectDetection: 'auto', - }, -}; - -export const mockProjectContext: ProjectContext = { - workspaceRoot: '/workspace', - projectRoot: '/workspace/project1', - templatePath: '/workspace/project1/templates', - configPath: '/workspace/project1/codebot.config.json', - isMultiProject: true, - projectName: 'project1', -}; - -export const mockTemplateType: TemplateType = { - name: 'ComponentSass', - path: '/workspace/templates/ComponentSass', - files: ['Component.tsx.hbs', 'Component.module.scss.hbs', 'index.tsx.hbs'], - hasSubdirectories: false, - structure: { - rootPath: '/workspace/templates/ComponentSass', - files: [ - { - name: 'Component.tsx.hbs', - path: '/workspace/templates/ComponentSass/Component.tsx.hbs', - relativePath: 'Component.tsx.hbs', - extension: '.hbs', - }, - { - name: 'Component.module.scss.hbs', - path: '/workspace/templates/ComponentSass/Component.module.scss.hbs', - relativePath: 'Component.module.scss.hbs', - extension: '.hbs', - }, - { - name: 'index.tsx.hbs', - path: '/workspace/templates/ComponentSass/index.tsx.hbs', - relativePath: 'index.tsx.hbs', - extension: '.hbs', - }, - ], - directories: [], - }, -}; - -export const mockSingleProjectContext: ProjectContext = { - workspaceRoot: '/workspace', - projectRoot: '/workspace', - templatePath: '/workspace/templates', - configPath: '/workspace/codebot.config.json', - isMultiProject: false, -}; diff --git a/src/test/managers/FileSystemManager.test.ts b/src/test/managers/FileSystemManager.test.ts deleted file mode 100644 index a260178..0000000 --- a/src/test/managers/FileSystemManager.test.ts +++ /dev/null @@ -1,375 +0,0 @@ -import * as fs from 'node:fs'; -import * as os from 'node:os'; -import * as path from 'node:path'; -import { FileSystemManager } from '../../managers/FileSystemManager'; -import { ErrorType } from '../../types'; - -describe('FileSystemManager', () => { - let fileSystemManager: FileSystemManager; - let tempDir: string; - - beforeEach(() => { - fileSystemManager = new FileSystemManager(); - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codebot-test-')); - }); - - afterEach(() => { - // Clean up temp directory - if (fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true, force: true }); - } - }); - - describe('createFile', () => { - it('should create a file with content', async () => { - const filePath = path.join(tempDir, 'test.txt'); - const content = 'Hello, World!'; - - await fileSystemManager.createFile(filePath, content); - - expect(fs.existsSync(filePath)).toBe(true); - expect(fs.readFileSync(filePath, 'utf8')).toBe(content); - }); - - it('should create parent directories if they do not exist', async () => { - const filePath = path.join(tempDir, 'nested', 'deep', 'test.txt'); - const content = 'Nested file content'; - - await fileSystemManager.createFile(filePath, content); - - expect(fs.existsSync(filePath)).toBe(true); - expect(fs.readFileSync(filePath, 'utf8')).toBe(content); - }); - }); - - describe('createFolder', () => { - it('should create a folder', async () => { - const folderPath = path.join(tempDir, 'testFolder'); - - await fileSystemManager.createFolder(folderPath); - - expect(fs.existsSync(folderPath)).toBe(true); - expect(fs.statSync(folderPath).isDirectory()).toBe(true); - }); - - it('should not throw error if folder already exists', async () => { - const folderPath = path.join(tempDir, 'existingFolder'); - fs.mkdirSync(folderPath); - - await expect( - fileSystemManager.createFolder(folderPath), - ).resolves.not.toThrow(); - }); - }); - - describe('createFolderRecursive', () => { - it('should create nested folder structure', async () => { - const folderPath = path.join(tempDir, 'level1', 'level2', 'level3'); - - await fileSystemManager.createFolderRecursive(folderPath); - - expect(fs.existsSync(folderPath)).toBe(true); - expect(fs.statSync(folderPath).isDirectory()).toBe(true); - }); - - it('should not throw error if folder structure already exists', async () => { - const folderPath = path.join(tempDir, 'existing', 'nested'); - fs.mkdirSync(folderPath, { recursive: true }); - - await expect( - fileSystemManager.createFolderRecursive(folderPath), - ).resolves.not.toThrow(); - }); - }); - - describe('fileExists', () => { - it('should return true for existing file', async () => { - const filePath = path.join(tempDir, 'existing.txt'); - fs.writeFileSync(filePath, 'content'); - - const exists = await fileSystemManager.fileExists(filePath); - - expect(exists).toBe(true); - }); - - it('should return false for non-existing file', async () => { - const filePath = path.join(tempDir, 'nonexistent.txt'); - - const exists = await fileSystemManager.fileExists(filePath); - - expect(exists).toBe(false); - }); - - it('should return false for directory path', async () => { - const dirPath = path.join(tempDir, 'directory'); - fs.mkdirSync(dirPath); - - const exists = await fileSystemManager.fileExists(dirPath); - - expect(exists).toBe(false); - }); - }); - - describe('folderExists', () => { - it('should return true for existing folder', async () => { - const folderPath = path.join(tempDir, 'existingFolder'); - fs.mkdirSync(folderPath); - - const exists = await fileSystemManager.folderExists(folderPath); - - expect(exists).toBe(true); - }); - - it('should return false for non-existing folder', async () => { - const folderPath = path.join(tempDir, 'nonexistentFolder'); - - const exists = await fileSystemManager.folderExists(folderPath); - - expect(exists).toBe(false); - }); - - it('should return false for file path', async () => { - const filePath = path.join(tempDir, 'file.txt'); - fs.writeFileSync(filePath, 'content'); - - const exists = await fileSystemManager.folderExists(filePath); - - expect(exists).toBe(false); - }); - }); - - describe('readFile', () => { - it('should read file content', async () => { - const filePath = path.join(tempDir, 'content.txt'); - const content = 'File content to read'; - fs.writeFileSync(filePath, content); - - const readContent = await fileSystemManager.readFile(filePath); - - expect(readContent).toBe(content); - }); - - it('should throw error for non-existing file', async () => { - const filePath = path.join(tempDir, 'nonexistent.txt'); - - await expect(fileSystemManager.readFile(filePath)).rejects.toMatchObject({ - type: ErrorType.FILE_SYSTEM_ERROR, - }); - }); - }); - - describe('listFiles', () => { - it('should list only files in directory', async () => { - const file1 = path.join(tempDir, 'file1.txt'); - const file2 = path.join(tempDir, 'file2.js'); - const subDir = path.join(tempDir, 'subdir'); - - fs.writeFileSync(file1, 'content1'); - fs.writeFileSync(file2, 'content2'); - fs.mkdirSync(subDir); - - const files = await fileSystemManager.listFiles(tempDir); - - expect(files).toHaveLength(2); - expect(files).toContain('file1.txt'); - expect(files).toContain('file2.js'); - expect(files).not.toContain('subdir'); - }); - - it('should return empty array for non-existing directory', async () => { - const nonExistentDir = path.join(tempDir, 'nonexistent'); - - const files = await fileSystemManager.listFiles(nonExistentDir); - - expect(files).toEqual([]); - }); - }); - - describe('listDirectories', () => { - it('should list only directories in directory', async () => { - const file1 = path.join(tempDir, 'file1.txt'); - const file2 = path.join(tempDir, 'file2.js'); - const subDir1 = path.join(tempDir, 'subdir1'); - const subDir2 = path.join(tempDir, 'subdir2'); - - fs.writeFileSync(file1, 'content1'); - fs.writeFileSync(file2, 'content2'); - fs.mkdirSync(subDir1); - fs.mkdirSync(subDir2); - - const directories = await fileSystemManager.listDirectories(tempDir); - - expect(directories).toHaveLength(2); - expect(directories).toContain('subdir1'); - expect(directories).toContain('subdir2'); - expect(directories).not.toContain('file1.txt'); - expect(directories).not.toContain('file2.js'); - }); - - it('should return empty array for non-existing directory', async () => { - const nonExistentDir = path.join(tempDir, 'nonexistent'); - - const directories = - await fileSystemManager.listDirectories(nonExistentDir); - - expect(directories).toEqual([]); - }); - }); - - describe('listFilesRecursive', () => { - beforeEach(() => { - // Create test directory structure - const structure = { - 'file1.txt': 'content1', - 'file2.js': 'content2', - 'subdir1/file3.ts': 'content3', - 'subdir1/file4.css': 'content4', - 'subdir1/nested/file5.html': 'content5', - 'subdir2/file6.json': 'content6', - }; - - for (const [filePath, content] of Object.entries(structure)) { - const fullPath = path.join(tempDir, filePath); - fs.mkdirSync(path.dirname(fullPath), { recursive: true }); - fs.writeFileSync(fullPath, content); - } - }); - - it('should list all files and directories recursively', async () => { - const entries = await fileSystemManager.listFilesRecursive(tempDir); - - // Should have root files and directories - expect(entries.length).toBeGreaterThan(0); - - // Check for root files - const rootFiles = entries.filter(e => !e.isDirectory); - expect(rootFiles.some(f => f.name === 'file1.txt')).toBe(true); - expect(rootFiles.some(f => f.name === 'file2.js')).toBe(true); - - // Check for directories - const directories = entries.filter(e => e.isDirectory); - expect(directories.some(d => d.name === 'subdir1')).toBe(true); - expect(directories.some(d => d.name === 'subdir2')).toBe(true); - - // Check nested structure - const subdir1 = directories.find(d => d.name === 'subdir1'); - expect(subdir1?.children).toBeDefined(); - expect(subdir1?.children?.some(c => c.name === 'file3.ts')).toBe(true); - expect( - subdir1?.children?.some(c => c.name === 'nested' && c.isDirectory), - ).toBe(true); - }); - - it('should set correct relative paths', async () => { - const entries = await fileSystemManager.listFilesRecursive(tempDir); - - const subdir1 = entries.find(e => e.name === 'subdir1' && e.isDirectory); - expect(subdir1?.relativePath).toBe('subdir1'); - - const nestedFile = subdir1?.children?.find(c => c.name === 'file3.ts'); - expect(nestedFile?.relativePath).toBe('file3.ts'); - }); - - it('should return empty array for non-existing directory', async () => { - const nonExistentDir = path.join(tempDir, 'nonexistent'); - - const entries = - await fileSystemManager.listFilesRecursive(nonExistentDir); - - expect(entries).toEqual([]); - }); - - it('should throw error when maximum depth is exceeded', async () => { - // Create a very deep directory structure (more than 15 levels) - let deepPath = tempDir; - for (let i = 0; i < 17; i++) { - deepPath = path.join(deepPath, `level${i}`); - fs.mkdirSync(deepPath, { recursive: true }); - } - - await expect( - fileSystemManager.listFilesRecursive(tempDir), - ).rejects.toMatchObject({ - type: ErrorType.FILE_SYSTEM_ERROR, - message: expect.stringContaining('Maximum directory depth exceeded'), - }); - }); - }); - - describe('copyDirectoryStructure', () => { - let sourceDir: string; - let targetDir: string; - - beforeEach(() => { - sourceDir = path.join(tempDir, 'source'); - targetDir = path.join(tempDir, 'target'); - - // Create source directory structure - const structure = { - 'file1.txt': 'content1', - 'file2.js': 'content2', - 'subdir1/file3.ts': 'content3', - 'subdir1/nested/file4.html': 'content4', - 'subdir2/file5.json': 'content5', - }; - - fs.mkdirSync(sourceDir); - for (const [filePath, content] of Object.entries(structure)) { - const fullPath = path.join(sourceDir, filePath); - fs.mkdirSync(path.dirname(fullPath), { recursive: true }); - fs.writeFileSync(fullPath, content); - } - }); - - it('should copy entire directory structure', async () => { - await fileSystemManager.copyDirectoryStructure(sourceDir, targetDir); - - // Verify target directory exists - expect(fs.existsSync(targetDir)).toBe(true); - - // Verify files are copied - expect(fs.existsSync(path.join(targetDir, 'file1.txt'))).toBe(true); - expect(fs.existsSync(path.join(targetDir, 'file2.js'))).toBe(true); - expect(fs.existsSync(path.join(targetDir, 'subdir1', 'file3.ts'))).toBe( - true, - ); - expect( - fs.existsSync(path.join(targetDir, 'subdir1', 'nested', 'file4.html')), - ).toBe(true); - expect(fs.existsSync(path.join(targetDir, 'subdir2', 'file5.json'))).toBe( - true, - ); - - // Verify content is copied correctly - expect(fs.readFileSync(path.join(targetDir, 'file1.txt'), 'utf8')).toBe( - 'content1', - ); - expect( - fs.readFileSync( - path.join(targetDir, 'subdir1', 'nested', 'file4.html'), - 'utf8', - ), - ).toBe('content4'); - }); - - it('should create target directory if it does not exist', async () => { - const deepTargetDir = path.join(tempDir, 'deep', 'nested', 'target'); - - await fileSystemManager.copyDirectoryStructure(sourceDir, deepTargetDir); - - expect(fs.existsSync(deepTargetDir)).toBe(true); - expect(fs.existsSync(path.join(deepTargetDir, 'file1.txt'))).toBe(true); - }); - - it('should throw error if source directory does not exist', async () => { - const nonExistentSource = path.join(tempDir, 'nonexistent'); - - await expect( - fileSystemManager.copyDirectoryStructure(nonExistentSource, targetDir), - ).rejects.toMatchObject({ - type: ErrorType.FILE_SYSTEM_ERROR, - message: expect.stringContaining('Source directory does not exist'), - }); - }); - }); -}); diff --git a/src/test/managers/TemplateManager.test.ts b/src/test/managers/TemplateManager.test.ts deleted file mode 100644 index f3e0f21..0000000 --- a/src/test/managers/TemplateManager.test.ts +++ /dev/null @@ -1,295 +0,0 @@ -import * as fs from 'node:fs'; -import * as os from 'node:os'; -import * as path from 'node:path'; -import { FileSystemManager } from '../../managers/FileSystemManager'; -import { TemplateManager } from '../../managers/TemplateManager'; -import { ErrorType, type TemplateStructure } from '../../types'; - -describe('TemplateManager', () => { - let templateManager: TemplateManager; - let fileSystemManager: FileSystemManager; - let tempDir: string; - let templatesDir: string; - - beforeEach(() => { - fileSystemManager = new FileSystemManager(); - templateManager = new TemplateManager(fileSystemManager); - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codebot-template-test-')); - templatesDir = path.join(tempDir, 'templates'); - }); - - afterEach(() => { - // Clean up temp directory - if (fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true, force: true }); - } - }); - - describe('scanTemplateStructure', () => { - beforeEach(async () => { - // Create test template structure - const structure = { - 'component.hbs': 'export const {{name}} = () => {};', - 'component.test.hbs': 'describe("{{name}}", () => {});', - 'styles/component.css.hbs': '.{{name}} { color: red; }', - 'styles/nested/theme.css.hbs': '.{{name}}-theme { background: blue; }', - 'utils/helper.js.hbs': 'export const {{name}}Helper = () => {};', - }; - - const templatePath = path.join(templatesDir, 'react-component'); - await fileSystemManager.createFolderRecursive(templatePath); - - for (const [filePath, content] of Object.entries(structure)) { - const fullPath = path.join(templatePath, filePath); - await fileSystemManager.createFile(fullPath, content); - } - }); - - it('should scan template structure with subdirectories', async () => { - const templatePath = path.join(templatesDir, 'react-component'); - - const structure = - await templateManager.scanTemplateStructure(templatePath); - - expect(structure.rootPath).toBe(templatePath); - expect(structure.files).toHaveLength(2); // component.hbs, component.test.hbs - expect(structure.directories).toHaveLength(2); // styles, utils - - // Check styles directory - const stylesDir = structure.directories.find(d => d.name === 'styles'); - expect(stylesDir).toBeDefined(); - expect(stylesDir?.files).toHaveLength(1); // component.css.hbs - expect(stylesDir?.subdirectories).toHaveLength(1); // nested - - // Check nested directory - const nestedDir = stylesDir?.subdirectories.find( - d => d.name === 'nested', - ); - expect(nestedDir).toBeDefined(); - expect(nestedDir?.files).toHaveLength(1); // theme.css.hbs - }); - - it('should throw error for non-existent template path', async () => { - const nonExistentPath = path.join(tempDir, 'nonexistent'); - - await expect( - templateManager.scanTemplateStructure(nonExistentPath), - ).rejects.toMatchObject({ - type: ErrorType.TEMPLATE_PROCESSING_ERROR, - }); - }); - }); - - describe('processTemplateHierarchy', () => { - let templateStructure: TemplateStructure; - - beforeEach(async () => { - // Create test template structure - const structure = { - 'component.hbs': 'export const {{name}} = () => {};', - 'component.test.hbs': 'describe("{{name}}", () => {});', - 'styles/component.css.hbs': '.{{name}} { color: red; }', - 'utils/helper.js.hbs': 'export const {{name}}Helper = () => {};', - }; - - const templatePath = path.join(templatesDir, 'react-component'); - await fileSystemManager.createFolderRecursive(templatePath); - - for (const [filePath, content] of Object.entries(structure)) { - const fullPath = path.join(templatePath, filePath); - await fileSystemManager.createFile(fullPath, content); - } - - templateStructure = - await templateManager.scanTemplateStructure(templatePath); - }); - - it('should process template hierarchy with component name', async () => { - const componentName = 'UserProfile'; - - const processed = await templateManager.processTemplateHierarchy( - templateStructure, - componentName, - ); - - expect(processed.componentName).toBe(componentName); - expect(processed.files).toHaveLength(2); - expect(processed.directories).toHaveLength(2); - - // Check processed files - const componentFile = processed.files.find(f => - f.targetPath.includes('component'), - ); - expect(componentFile?.content).toContain('UserProfile'); - - // Check processed directories - const stylesDir = processed.directories.find(d => - d.targetPath.includes('styles'), - ); - expect(stylesDir).toBeDefined(); - expect(stylesDir?.files).toHaveLength(1); - - const cssFile = stylesDir?.files[0]; - expect(cssFile?.content).toContain('UserProfile'); - }); - - it('should apply naming rules to file paths', async () => { - const componentName = 'UserProfile'; - - const processed = await templateManager.processTemplateHierarchy( - templateStructure, - componentName, - ); - - // Files should have .hbs extension removed - for (const file of processed.files) { - expect(file.targetPath).not.toContain('.hbs'); - } - - // Check if template naming is applied - const componentFile = processed.files.find(f => - f.targetPath.includes('component'), - ); - expect(componentFile?.targetPath).toContain('component'); // Should keep original name if no template placeholder - }); - }); - - describe('discoverTemplates with hierarchical support', () => { - beforeEach(async () => { - // Create multiple template types with different structures - const templates = { - 'react-component': { - 'component.hbs': 'export const {{name}} = () => {};', - 'styles/component.css.hbs': '.{{name}} { color: red; }', - }, - 'vue-component': { - 'template.vue.hbs': '', - 'assets/icon.svg.hbs': '{{name}}', - }, - 'simple-file': { - 'file.js.hbs': 'const {{name}} = {};', - }, - }; - - await fileSystemManager.createFolderRecursive(templatesDir); - - for (const [templateName, files] of Object.entries(templates)) { - const templatePath = path.join(templatesDir, templateName); - await fileSystemManager.createFolderRecursive(templatePath); - - for (const [filePath, content] of Object.entries(files)) { - const fullPath = path.join(templatePath, filePath); - await fileSystemManager.createFile(fullPath, content); - } - } - }); - - it('should discover templates with subdirectory information', async () => { - // Debug: Check if templates directory exists and has content - const templatesExist = await fileSystemManager.folderExists(templatesDir); - expect(templatesExist).toBe(true); - - const templateEntries = - await fileSystemManager.listFilesRecursive(templatesDir); - const templateDirs = templateEntries.filter(e => e.isDirectory); - expect(templateDirs.some(d => d.name === 'react-component')).toBe(true); - expect(templateDirs.some(d => d.name === 'vue-component')).toBe(true); - expect(templateDirs.some(d => d.name === 'simple-file')).toBe(true); - - const templates = await templateManager.discoverTemplates(tempDir); - - expect(templates).toHaveLength(3); - - // Check react-component template - const reactTemplate = templates.find(t => t.name === 'react-component'); - expect(reactTemplate?.hasSubdirectories).toBe(true); - expect(reactTemplate?.structure.directories).toHaveLength(1); - - // Check vue-component template - const vueTemplate = templates.find(t => t.name === 'vue-component'); - expect(vueTemplate?.hasSubdirectories).toBe(true); - expect(vueTemplate?.structure.directories).toHaveLength(1); - - // Check simple-file template - const simpleTemplate = templates.find(t => t.name === 'simple-file'); - expect(simpleTemplate?.hasSubdirectories).toBe(false); - expect(simpleTemplate?.structure.directories).toHaveLength(0); - }); - }); - - describe('processTemplate (updated async version)', () => { - beforeEach(async () => { - const templatePath = path.join(templatesDir, 'test-template'); - await fileSystemManager.createFolderRecursive(templatePath); - - const templateFile = path.join(templatePath, 'component.hbs'); - await fileSystemManager.createFile( - templateFile, - 'export const {{name}} = () => {};', - ); - }); - - it('should process template content with component name', async () => { - const templateFile = path.join( - templatesDir, - 'test-template', - 'component.hbs', - ); - const componentName = 'UserProfile'; - - const result = await templateManager.processTemplate( - templateFile, - componentName, - ); - - expect(result).toBe('export const UserProfile = () => {};'); - }); - - it('should throw error for non-existent template file', async () => { - const nonExistentFile = path.join(templatesDir, 'nonexistent.hbs'); - const componentName = 'Test'; - - await expect( - templateManager.processTemplate(nonExistentFile, componentName), - ).rejects.toMatchObject({ - type: ErrorType.TEMPLATE_PROCESSING_ERROR, - }); - }); - }); - - describe('cache functionality', () => { - beforeEach(async () => { - const templatePath = path.join(templatesDir, 'cached-template'); - await fileSystemManager.createFolderRecursive(templatePath); - - const templateFile = path.join(templatePath, 'component.hbs'); - await fileSystemManager.createFile( - templateFile, - 'export const {{name}} = () => {};', - ); - }); - - it('should cache template discovery results', async () => { - // First call - const templates1 = await templateManager.discoverTemplates(tempDir); - - // Second call should use cache - const templates2 = await templateManager.discoverTemplates(tempDir); - - expect(templates1).toEqual(templates2); - expect(templates1.length).toBeGreaterThanOrEqual(0); // May be 0 if no templates found - }); - - it('should invalidate cache when requested', async () => { - // Discover templates - const templates1 = await templateManager.discoverTemplates(tempDir); - - // Invalidate cache - templateManager.invalidateCache(); - - // Discover templates again - should work without errors - const templates2 = await templateManager.discoverTemplates(tempDir); - expect(templates2).toEqual(templates1); - }); - }); -}); diff --git a/src/test/setup.ts b/src/test/setup.ts deleted file mode 100644 index 24f89eb..0000000 --- a/src/test/setup.ts +++ /dev/null @@ -1,12 +0,0 @@ -// Setup global para testes -import 'jest'; - -// Mock global do console para evitar logs desnecessários durante os testes -global.console = { - ...console, - log: jest.fn(), - debug: jest.fn(), - info: jest.fn(), - warn: jest.fn(), - error: jest.fn(), -}; diff --git a/src/test/unit/commands/updateComponent.test.ts b/src/test/unit/commands/updateComponent.test.ts deleted file mode 100644 index c69c9d7..0000000 --- a/src/test/unit/commands/updateComponent.test.ts +++ /dev/null @@ -1,297 +0,0 @@ -import * as fs from 'node:fs'; -import { updateComponent } from '../../../commands/updateComponent'; - -// Mock VS Code module -jest.mock('vscode', () => ({ - workspace: { - workspaceFolders: [ - { - uri: { - fsPath: '/test/workspace', - }, - }, - ], - }, -})); - -// Mock helpers -jest.mock('../../../helpers', () => ({ - showMessage: jest.fn(), - showSearchDropdown: jest.fn(), - createFolder: jest.fn(), - createFile: jest.fn(), - checkExistsFile: jest.fn(), - formatTemplateName: jest.fn((templateFile, templateType, componentName) => { - return templateFile - .replace('.hbs', '') - .replace(templateType, componentName); - }), -})); - -// Mock fs operations -jest.mock('fs', () => ({ - existsSync: jest.fn(), - readdirSync: jest.fn(), - readFileSync: jest.fn(), - promises: { - access: jest.fn(), - readdir: jest.fn(), - readFile: jest.fn(), - }, -})); - -// Mock FileSystemManager -jest.mock('../../../managers/FileSystemManager', () => ({ - FileSystemManager: jest.fn().mockImplementation(() => ({ - folderExists: jest.fn().mockResolvedValue(true), - listDirectories: jest.fn().mockResolvedValue(['ComponentTemplate']), - listFiles: jest - .fn() - .mockResolvedValue(['component.tsx.hbs', 'component.test.tsx.hbs']), - readFile: jest.fn().mockResolvedValue('Hello {{name}}!'), - listFilesRecursive: jest.fn().mockResolvedValue([ - { - name: 'component.tsx.hbs', - path: 'component.tsx.hbs', - isDirectory: false, - }, - { - name: 'component.test.tsx.hbs', - path: 'component.test.tsx.hbs', - isDirectory: false, - }, - ]), - })), -})); - -// Mock DirectoryProcessor -jest.mock('../../../utils/DirectoryProcessor', () => ({ - DirectoryProcessor: jest.fn().mockImplementation(() => ({ - scanTemplateStructure: jest.fn().mockResolvedValue({ - rootPath: '/test/templates/ComponentTemplate', - files: [ - { - name: 'component.tsx.hbs', - path: 'component.tsx.hbs', - relativePath: 'component.tsx.hbs', - }, - { - name: 'component.test.tsx.hbs', - path: 'component.test.tsx.hbs', - relativePath: 'component.test.tsx.hbs', - }, - ], - directories: [], - }), - validateDepth: jest.fn(), - })), -})); - -describe('updateComponent', () => { - const mockHelpers = require('../../../helpers'); - const mockFs = fs as jest.Mocked; - - beforeEach(() => { - jest.clearAllMocks(); - - // Setup default mocks - mockFs.existsSync.mockReturnValue(true); - (mockFs.readdirSync as jest.Mock).mockReturnValue(['ComponentTemplate']); - mockFs.readFileSync.mockReturnValue('{"templateFolderPath": "templates"}'); - - // Mock successful template discovery - (mockFs.promises.access as jest.Mock).mockResolvedValue(undefined); - (mockFs.promises.readdir as jest.Mock).mockResolvedValue([ - { name: 'ComponentTemplate', isDirectory: () => true }, - ]); - (mockFs.promises.readFile as jest.Mock).mockResolvedValue( - 'Hello {{name}}!', - ); - - mockHelpers.checkExistsFile.mockReturnValue(true); - mockHelpers.showSearchDropdown.mockResolvedValue('ComponentTemplate'); - - // Reset FileSystemManager mock to default successful state - const MockedFileSystemManager = - require('../../../managers/FileSystemManager').FileSystemManager; - MockedFileSystemManager.mockClear(); - MockedFileSystemManager.mockImplementation(() => ({ - folderExists: jest.fn().mockResolvedValue(true), - listDirectories: jest.fn().mockResolvedValue(['ComponentTemplate']), - listFiles: jest - .fn() - .mockResolvedValue(['component.tsx.hbs', 'component.test.tsx.hbs']), - readFile: jest.fn().mockResolvedValue('Hello {{name}}!'), - listFilesRecursive: jest.fn().mockResolvedValue([ - { - name: 'component.tsx.hbs', - path: 'component.tsx.hbs', - isDirectory: false, - }, - { - name: 'component.test.tsx.hbs', - path: 'component.test.tsx.hbs', - isDirectory: false, - }, - ]), - })); - - // Reset DirectoryProcessor mock to default successful state - const MockedDirectoryProcessor = - require('../../../utils/DirectoryProcessor').DirectoryProcessor; - MockedDirectoryProcessor.mockClear(); - MockedDirectoryProcessor.mockImplementation(() => ({ - scanTemplateStructure: jest.fn().mockResolvedValue({ - rootPath: '/test/templates/ComponentTemplate', - files: [ - { - name: 'component.tsx.hbs', - path: 'component.tsx.hbs', - relativePath: 'component.tsx.hbs', - }, - { - name: 'component.test.tsx.hbs', - path: 'component.test.tsx.hbs', - relativePath: 'component.test.tsx.hbs', - }, - ], - directories: [], - }), - validateDepth: jest.fn(), - })); - }); - - it('should successfully update a component with multi-project support', async () => { - // Arrange - const args = { - fsPath: '/test/workspace/project1/components/MyComponent', - }; - - // Mock that one file exists, one doesn't - mockHelpers.checkExistsFile - .mockReturnValueOnce(true) // component folder exists - .mockReturnValueOnce(true) // first file exists (skip) - .mockReturnValueOnce(false); // second file doesn't exist (create) - - // Act - await updateComponent(args); - - // Assert - expect(mockHelpers.createFile).toHaveBeenCalledTimes(1); - expect(mockHelpers.showMessage).toHaveBeenCalledWith( - expect.stringContaining("Component 'MyComponent' updated successfully!"), - 'information', - ); - }); - - it('should handle case when no templates are found', async () => { - // Arrange - const args = { - fsPath: '/test/workspace/project1/components/MyComponent', - }; - - // Mock FileSystemManager to return no templates - const MockedFileSystemManager = - require('../../../managers/FileSystemManager').FileSystemManager; - MockedFileSystemManager.mockImplementation(() => ({ - folderExists: jest.fn().mockResolvedValue(false), // No template folder exists - listDirectories: jest.fn().mockResolvedValue([]), - listFiles: jest.fn().mockResolvedValue([]), - readFile: jest.fn().mockResolvedValue('Hello {{name}}!'), - listFilesRecursive: jest.fn().mockResolvedValue([]), - })); - - // Act - await updateComponent(args); - - // Assert - expect(mockHelpers.showMessage).toHaveBeenCalledWith( - expect.stringContaining('No templates found in project'), - 'error', - ); - }); - - it('should handle invalid component name', async () => { - // Arrange - const args = { - fsPath: '/test/workspace/project1/components/', - }; - - // Mock FileSystemManager to return no templates - const MockedFileSystemManager = - require('../../../managers/FileSystemManager').FileSystemManager; - MockedFileSystemManager.mockImplementation(() => ({ - folderExists: jest.fn().mockResolvedValue(false), // No template folder exists - listDirectories: jest.fn().mockResolvedValue([]), - listFiles: jest.fn().mockResolvedValue([]), - readFile: jest.fn().mockResolvedValue('Hello {{name}}!'), - listFilesRecursive: jest.fn().mockResolvedValue([]), - })); - - // Act - await updateComponent(args); - - // Assert - expect(mockHelpers.showMessage).toHaveBeenCalledWith( - expect.stringContaining('No templates found in project'), - 'error', - ); - }); - - it('should handle missing folder path', async () => { - // Arrange - const args = {}; - - // Act - await updateComponent(args); - - // Assert - expect(mockHelpers.showMessage).toHaveBeenCalledWith( - expect.stringContaining('No folder path provided'), - 'error', - ); - }); - - it('should skip existing files and only create missing ones', async () => { - // Arrange - const args = { - fsPath: '/test/workspace/project1/components/MyComponent', - }; - - // Mock file existence: file1 exists, file2 and file3 don't exist - mockHelpers.checkExistsFile - .mockReturnValueOnce(true) // component folder exists - .mockReturnValueOnce(true) // file1 exists (skip) - .mockReturnValueOnce(false); // file2 doesn't exist (create) - - // Act - await updateComponent(args); - - // Assert - expect(mockHelpers.createFile).toHaveBeenCalledTimes(1); // Only file2 - expect(mockHelpers.showMessage).toHaveBeenCalledWith( - expect.stringContaining('1 files added, 1 files already exist'), - 'information', - ); - }); - - it('should show appropriate message when all files already exist', async () => { - // Arrange - const args = { - fsPath: '/test/workspace/project1/components/MyComponent', - }; - - // Mock all files exist - mockHelpers.checkExistsFile.mockReturnValue(true); - - // Act - await updateComponent(args); - - // Assert - expect(mockHelpers.createFile).not.toHaveBeenCalled(); - expect(mockHelpers.showMessage).toHaveBeenCalledWith( - expect.stringContaining('is already up to date'), - 'information', - ); - }); -}); diff --git a/src/test/unit/managers/ConfigurationManager.test.ts b/src/test/unit/managers/ConfigurationManager.test.ts deleted file mode 100644 index b147699..0000000 --- a/src/test/unit/managers/ConfigurationManager.test.ts +++ /dev/null @@ -1,274 +0,0 @@ -import * as path from 'node:path'; -import { DEFAULT_CONFIGURATION } from '../../../constants'; -import { ConfigurationManager } from '../../../managers/ConfigurationManager'; -import type { ConfigurationFile } from '../../../types'; - -// Mock fs module -jest.mock('fs', () => ({ - promises: { - readFile: jest.fn(), - writeFile: jest.fn(), - }, - readFileSync: jest.fn(), -})); - -// Mock vscode module -jest.mock('vscode', () => ({ - workspace: { - workspaceFolders: [ - { - uri: { - fsPath: '/mock/workspace', - }, - }, - ], - }, -})); - -describe('ConfigurationManager', () => { - let configManager: ConfigurationManager; - const mockFs = require('node:fs'); - - beforeEach(() => { - jest.clearAllMocks(); - configManager = new ConfigurationManager(); - }); - - describe('getConfiguration', () => { - it('should return project-specific configuration when available', async () => { - const projectConfig: ConfigurationFile = { - templateFolderPath: 'custom-templates', - multiProject: { - enabled: false, - projectDetection: 'manual', - }, - }; - - mockFs.promises.readFile.mockResolvedValueOnce( - JSON.stringify(projectConfig), - ); - - const result = await configManager.getConfiguration( - '/mock/workspace/project1', - ); - - expect(result).toEqual(projectConfig); - expect(mockFs.promises.readFile).toHaveBeenCalledWith( - '/mock/workspace/project1/codebot.config.json', - 'utf8', - ); - }); - - it('should fallback to workspace configuration when project config not found', async () => { - const workspaceConfig: ConfigurationFile = { - templateFolderPath: 'shared-templates', - }; - - mockFs.promises.readFile - .mockRejectedValueOnce(new Error('File not found')) // Project config - .mockResolvedValueOnce(JSON.stringify(workspaceConfig)); // Workspace config - - const result = await configManager.getConfiguration( - '/mock/workspace/project1', - ); - - expect(result.templateFolderPath).toBe('shared-templates'); - }); - - it('should use default configuration when no config files found', async () => { - mockFs.promises.readFile.mockRejectedValue(new Error('File not found')); - - const result = await configManager.getConfiguration( - '/mock/workspace/project1', - ); - - expect(result).toEqual(DEFAULT_CONFIGURATION); - }); - - it('should merge configurations hierarchically', async () => { - const workspaceConfig: ConfigurationFile = { - templateFolderPath: 'workspace-templates', - multiProject: { - enabled: true, - projectDetection: 'auto', - }, - }; - - const projectConfig: Partial = { - templateFolderPath: 'project-templates', - // multiProject not specified, should inherit from workspace - }; - - mockFs.promises.readFile - .mockResolvedValueOnce(JSON.stringify(projectConfig)) // Project config - .mockResolvedValueOnce(JSON.stringify(workspaceConfig)); // Workspace config - - const result = await configManager.getConfiguration( - '/mock/workspace/project1', - ); - - expect(result).toEqual({ - templateFolderPath: 'project-templates', // From project config - multiProject: { - enabled: true, - projectDetection: 'auto', - }, // From workspace config - }); - }); - - it('should cache configuration results', async () => { - const config: ConfigurationFile = { templateFolderPath: 'templates' }; - mockFs.promises.readFile.mockResolvedValue(JSON.stringify(config)); - - const result1 = await configManager.getConfiguration( - '/mock/workspace/project1', - ); - const result2 = await configManager.getConfiguration( - '/mock/workspace/project1', - ); - - expect(result1).toEqual(result2); // Should have same content - expect(mockFs.promises.readFile).toHaveBeenCalledTimes(2); // Called for project and workspace - }); - - it('should return default configuration when files cannot be read', async () => { - mockFs.promises.readFile.mockRejectedValue( - new Error('Permission denied'), - ); - - const result = await configManager.getConfiguration( - '/mock/workspace/project1', - ); - - expect(result).toEqual(DEFAULT_CONFIGURATION); - }); - }); - - describe('resolveTemplatePath', () => { - it('should resolve template path using configuration', () => { - const config: ConfigurationFile = { - templateFolderPath: 'custom-templates', - }; - mockFs.readFileSync.mockReturnValueOnce(JSON.stringify(config)); - - const result = configManager.resolveTemplatePath( - '/mock/workspace/project1', - ); - - expect(result).toBe( - path.resolve('/mock/workspace/project1', 'custom-templates'), - ); - }); - - it('should use default template folder when config not found', () => { - mockFs.readFileSync.mockImplementation(() => { - throw new Error('File not found'); - }); - - const result = configManager.resolveTemplatePath( - '/mock/workspace/project1', - ); - - expect(result).toBe( - path.resolve('/mock/workspace/project1', 'templates'), - ); - }); - }); - - describe('validateConfiguration', () => { - it('should validate correct configuration', () => { - const validConfig: ConfigurationFile = { - templateFolderPath: 'templates', - multiProject: { - enabled: true, - projectDetection: 'auto', - }, - }; - - expect(configManager.validateConfiguration(validConfig)).toBe(true); - }); - - it('should reject invalid templateFolderPath', () => { - const invalidConfig = { - templateFolderPath: 123, // Should be string - } as unknown as ConfigurationFile; - - expect(configManager.validateConfiguration(invalidConfig)).toBe(false); - }); - - it('should reject invalid multiProject.projectDetection', () => { - const invalidConfig: ConfigurationFile = { - multiProject: { - enabled: true, - projectDetection: 'invalid' as unknown as 'auto' | 'manual', - }, - }; - - expect(configManager.validateConfiguration(invalidConfig)).toBe(false); - }); - }); - - describe('createDefaultConfiguration', () => { - it('should create default configuration file', async () => { - await configManager.createDefaultConfiguration( - '/mock/workspace/project1', - ); - - expect(mockFs.promises.writeFile).toHaveBeenCalledWith( - '/mock/workspace/project1/codebot.config.json', - expect.stringContaining('"templateFolderPath": "templates"'), - 'utf8', - ); - }); - - it('should invalidate cache after creating configuration', async () => { - // First, cache a configuration - mockFs.promises.readFile.mockResolvedValue('{}'); - await configManager.getConfiguration('/mock/workspace/project1'); - - // Create default configuration - await configManager.createDefaultConfiguration( - '/mock/workspace/project1', - ); - - // Next call should read from file again (cache invalidated) - mockFs.promises.readFile.mockResolvedValue( - '{"templateFolderPath": "new-templates"}', - ); - await configManager.getConfiguration('/mock/workspace/project1'); - - expect(mockFs.promises.readFile).toHaveBeenCalledTimes(4); // 2 for first call + 2 for second call - }); - }); - - describe('cache management', () => { - it('should clear all cache', async () => { - mockFs.promises.readFile.mockResolvedValue('{}'); - - await configManager.getConfiguration('/mock/workspace/project1'); - await configManager.getConfiguration('/mock/workspace/project2'); - - configManager.clearCache(); - - // Next calls should read from file again - await configManager.getConfiguration('/mock/workspace/project1'); - - expect(mockFs.promises.readFile).toHaveBeenCalledTimes(6); // 2 + 2 + 2 (each call tries project and workspace) - }); - - it('should invalidate specific configuration', async () => { - mockFs.promises.readFile.mockResolvedValue('{}'); - - await configManager.getConfiguration('/mock/workspace/project1'); - await configManager.getConfiguration('/mock/workspace/project2'); - - configManager.invalidateConfiguration('/mock/workspace/project1'); - - // Only project1 should be re-read - await configManager.getConfiguration('/mock/workspace/project1'); - await configManager.getConfiguration('/mock/workspace/project2'); - - expect(mockFs.promises.readFile).toHaveBeenCalledTimes(6); // 2 + 2 + 2 (project1 re-read, project2 cached) - }); - }); -}); diff --git a/src/test/unit/managers/ProjectDetector.test.ts b/src/test/unit/managers/ProjectDetector.test.ts deleted file mode 100644 index cb94379..0000000 --- a/src/test/unit/managers/ProjectDetector.test.ts +++ /dev/null @@ -1,213 +0,0 @@ -import * as path from 'node:path'; -import * as vscode from 'vscode'; -import { CodebotError } from '../../../errors'; -import { ProjectDetector } from '../../../managers/ProjectDetector'; - -// Mock vscode module -jest.mock('vscode', () => ({ - workspace: { - workspaceFolders: [ - { - uri: { - fsPath: '/mock/workspace', - }, - }, - ], - }, -})); - -// Mock fs module -jest.mock('node:fs', () => ({ - readdirSync: jest.fn(), -})); - -type WorkspaceMock = { - workspaceFolders: Array<{ uri: { fsPath: string } }> | undefined; -}; - -describe('ProjectDetector', () => { - let projectDetector: ProjectDetector; - const mockFs = require('node:fs'); - const workspaceMock = vscode.workspace as unknown as WorkspaceMock; - - beforeEach(() => { - jest.clearAllMocks(); - - // Reset vscode mock to default state - workspaceMock.workspaceFolders = [{ uri: { fsPath: '/mock/workspace' } }]; - - projectDetector = new ProjectDetector(); - }); - - describe('detectProject', () => { - it('should detect single project context', async () => { - // Mock single workspace folder - workspaceMock.workspaceFolders = [{ uri: { fsPath: '/mock/workspace' } }]; - - mockFs.readdirSync.mockReturnValue([ - { name: 'src', isDirectory: () => true }, - { name: 'package.json', isDirectory: () => false }, - ]); - - projectDetector = new ProjectDetector(); - const context = await projectDetector.detectProject( - '/mock/workspace/src', - ); - - expect(context).toEqual({ - workspaceRoot: '/mock/workspace', - projectRoot: '/mock/workspace', - templatePath: path.join('/mock/workspace', 'templates'), - configPath: path.join('/mock/workspace', 'codebot.config.json'), - isMultiProject: false, - projectName: undefined, - }); - }); - - it('should detect multi-project context', async () => { - // Mock multiple workspace folders - workspaceMock.workspaceFolders = [ - { uri: { fsPath: '/mock/workspace/project1' } }, - { uri: { fsPath: '/mock/workspace/project2' } }, - ]; - - projectDetector = new ProjectDetector(); - const context = await projectDetector.detectProject( - '/mock/workspace/project1/src', - ); - - expect(context.isMultiProject).toBe(true); - expect(context.projectName).toBeDefined(); - }); - - it('should cache project contexts', async () => { - // Mock single workspace folder - workspaceMock.workspaceFolders = [{ uri: { fsPath: '/mock/workspace' } }]; - - mockFs.readdirSync.mockReturnValue([ - { name: 'src', isDirectory: () => true }, - { name: 'package.json', isDirectory: () => false }, - ]); - - projectDetector = new ProjectDetector(); - const context1 = await projectDetector.detectProject( - '/mock/workspace/src', - ); - const context2 = await projectDetector.detectProject( - '/mock/workspace/src', - ); - - expect(context1).toEqual(context2); // Should have the same content - }); - - it('should handle detection errors gracefully', async () => { - // Mock workspace folders to be empty to trigger error - workspaceMock.workspaceFolders = []; - - await expect(() => new ProjectDetector()).toThrow(CodebotError); - }); - }); - - describe('isMultiProjectWorkspace', () => { - it('should return true for multiple workspace folders', () => { - workspaceMock.workspaceFolders = [ - { uri: { fsPath: '/mock/workspace/project1' } }, - { uri: { fsPath: '/mock/workspace/project2' } }, - ]; - - projectDetector = new ProjectDetector(); - expect(projectDetector.isMultiProjectWorkspace()).toBe(true); - }); - - it('should return false for single workspace folder with no sub-projects', () => { - workspaceMock.workspaceFolders = [{ uri: { fsPath: '/mock/workspace' } }]; - - mockFs.readdirSync.mockReturnValue([ - { name: 'src', isDirectory: () => true }, - { name: 'package.json', isDirectory: () => false }, - ]); - - projectDetector = new ProjectDetector(); - expect(projectDetector.isMultiProjectWorkspace()).toBe(false); - }); - - it('should return true for single workspace with multiple sub-projects', () => { - workspaceMock.workspaceFolders = [{ uri: { fsPath: '/mock/workspace' } }]; - - mockFs.readdirSync - .mockReturnValueOnce([ - { name: 'project1', isDirectory: () => true }, - { name: 'project2', isDirectory: () => true }, - ]) - .mockReturnValue(['package.json']); // For looksLikeProject calls - - projectDetector = new ProjectDetector(); - expect(projectDetector.isMultiProjectWorkspace()).toBe(true); - }); - }); - - describe('resolveProjectRoot', () => { - it('should return workspace root for single project', () => { - mockFs.readdirSync.mockReturnValue([]); - - const projectRoot = projectDetector.resolveProjectRoot( - '/mock/workspace/src/components', - ); - expect(projectRoot).toBe('/mock/workspace'); - }); - - it('should find nearest project root in multi-project workspace', () => { - workspaceMock.workspaceFolders = [ - { uri: { fsPath: '/mock/workspace/project1' } }, - { uri: { fsPath: '/mock/workspace/project2' } }, - ]; - - mockFs.readdirSync.mockReturnValue([ - { name: 'package.json', isDirectory: () => false }, - ]); - - projectDetector = new ProjectDetector(); - const projectRoot = projectDetector.resolveProjectRoot( - '/mock/workspace/project1/src', - ); - - expect(projectRoot).toBe('/mock/workspace/project1'); - }); - }); - - describe('cache management', () => { - it('should clear cache', async () => { - // Mock single workspace folder - workspaceMock.workspaceFolders = [{ uri: { fsPath: '/mock/workspace' } }]; - - mockFs.readdirSync.mockReturnValue([ - { name: 'src', isDirectory: () => true }, - { name: 'package.json', isDirectory: () => false }, - ]); - - projectDetector = new ProjectDetector(); - await projectDetector.detectProject('/mock/workspace/src'); - expect(projectDetector.getAllProjects()).toHaveLength(1); - - projectDetector.clearCache(); - expect(projectDetector.getAllProjects()).toHaveLength(0); - }); - - it('should return all cached projects', async () => { - // Mock single workspace folder - workspaceMock.workspaceFolders = [{ uri: { fsPath: '/mock/workspace' } }]; - - mockFs.readdirSync.mockReturnValue([ - { name: 'src', isDirectory: () => true }, - { name: 'package.json', isDirectory: () => false }, - ]); - - projectDetector = new ProjectDetector(); - await projectDetector.detectProject('/mock/workspace/src'); - await projectDetector.detectProject('/mock/workspace/lib'); - - const projects = projectDetector.getAllProjects(); - expect(projects).toHaveLength(2); - }); - }); -}); diff --git a/src/test/unit/managers/TemplateManager.test.ts b/src/test/unit/managers/TemplateManager.test.ts deleted file mode 100644 index aa1566a..0000000 --- a/src/test/unit/managers/TemplateManager.test.ts +++ /dev/null @@ -1,796 +0,0 @@ -import { TemplateManager } from '../../../managers/TemplateManager'; -import type { ProcessedTemplate } from '../../../types'; - -type TemplateManagerTestable = { - validateProcessedTemplate(processedTemplate: ProcessedTemplate): void; - collectAllTargetPaths(processedTemplate: ProcessedTemplate): string[]; - findDuplicatePaths(targetPaths: string[]): string[]; -}; - -// Mock fs module -jest.mock('fs', () => ({ - promises: { - access: jest.fn(), - readdir: jest.fn(), - readFile: jest.fn(), - }, - readFileSync: jest.fn(), - readdirSync: jest.fn(), -})); - -// Mock FileSystemManager -jest.mock('../../../managers/FileSystemManager', () => ({ - FileSystemManager: jest.fn().mockImplementation(() => ({ - folderExists: jest.fn().mockResolvedValue(true), - listDirectories: jest - .fn() - .mockResolvedValue(['ComponentSass', 'ComponentStyled']), - listFiles: jest - .fn() - .mockResolvedValue(['Component.tsx.hbs', 'index.tsx.hbs']), - readFile: jest.fn().mockResolvedValue('Hello {{name}}!'), - listFilesRecursive: jest.fn().mockResolvedValue([ - { - name: 'Component.tsx.hbs', - path: 'Component.tsx.hbs', - isDirectory: false, - }, - { name: 'index.tsx.hbs', path: 'index.tsx.hbs', isDirectory: false }, - ]), - })), -})); - -// Mock DirectoryProcessor -jest.mock('../../../utils/DirectoryProcessor', () => ({ - DirectoryProcessor: jest.fn().mockImplementation(() => ({ - scanTemplateStructure: jest.fn().mockResolvedValue({ - rootPath: '/test/templates/ComponentTemplate', - files: [ - { - name: 'Component.tsx.hbs', - path: 'Component.tsx.hbs', - relativePath: 'Component.tsx.hbs', - }, - { - name: 'index.tsx.hbs', - path: 'index.tsx.hbs', - relativePath: 'index.tsx.hbs', - }, - ], - directories: [], - }), - validateDepth: jest.fn(), - })), -})); - -// Mock handlebars -jest.mock('handlebars', () => ({ - compile: jest.fn(), -})); - -// Mock vscode module -jest.mock('vscode', () => ({ - workspace: { - workspaceFolders: [ - { - uri: { - fsPath: '/mock/workspace', - }, - }, - ], - }, -})); - -describe('TemplateManager', () => { - let templateManager: TemplateManager; - const mockFs = require('node:fs'); - const mockHandlebars = require('handlebars'); - - beforeEach(() => { - jest.clearAllMocks(); - - // Reset FileSystemManager mock to default successful state - const MockedFileSystemManager = - require('../../../managers/FileSystemManager').FileSystemManager; - MockedFileSystemManager.mockClear(); - MockedFileSystemManager.mockImplementation(() => ({ - folderExists: jest.fn().mockResolvedValue(true), - listDirectories: jest - .fn() - .mockResolvedValue(['ComponentSass', 'ComponentStyled']), - listFiles: jest - .fn() - .mockResolvedValue(['Component.tsx.hbs', 'index.tsx.hbs']), - readFile: jest.fn().mockResolvedValue('Hello {{name}}!'), - listFilesRecursive: jest.fn().mockResolvedValue([ - { - name: 'Component.tsx.hbs', - path: 'Component.tsx.hbs', - isDirectory: false, - }, - { name: 'index.tsx.hbs', path: 'index.tsx.hbs', isDirectory: false }, - ]), - })); - - templateManager = new TemplateManager(); - }); - - describe('discoverTemplates', () => { - it('should discover templates in project directory', async () => { - mockFs.promises.access.mockResolvedValue(undefined); - mockFs.promises.readdir - .mockResolvedValueOnce([ - { name: 'ComponentSass', isDirectory: () => true }, - { name: 'ComponentStyled', isDirectory: () => true }, - ]) - .mockResolvedValueOnce(['Component.tsx.hbs', 'index.tsx.hbs']) - .mockResolvedValueOnce(['index.tsx.hbs', 'styles.ts.hbs']); - - const templates = await templateManager.discoverTemplates( - '/mock/workspace/project1', - ); - - expect(templates).toHaveLength(2); - expect(templates[0]).toEqual( - expect.objectContaining({ - name: 'ComponentSass', - path: '/mock/workspace/project1/templates/ComponentSass', - files: ['Component.tsx.hbs', 'index.tsx.hbs'], - }), - ); - expect(templates[1]).toEqual( - expect.objectContaining({ - name: 'ComponentStyled', - path: '/mock/workspace/project1/templates/ComponentStyled', - files: ['Component.tsx.hbs', 'index.tsx.hbs'], - }), - ); - }); - - it('should fallback to workspace templates when project templates not found', async () => { - // Mock FileSystemManager to return no templates for project, but templates for workspace - const MockedFileSystemManager = - require('../../../managers/FileSystemManager').FileSystemManager; - MockedFileSystemManager.mockImplementation(() => ({ - folderExists: jest - .fn() - .mockResolvedValueOnce(false) // Project templates don't exist - .mockResolvedValueOnce(true), // Workspace templates exist - listDirectories: jest.fn().mockResolvedValue(['SharedComponent']), - listFiles: jest.fn().mockResolvedValue(['Component.tsx.hbs']), - readFile: jest.fn().mockResolvedValue('Hello {{name}}!'), - listFilesRecursive: jest.fn().mockResolvedValue([ - { - name: 'Component.tsx.hbs', - path: 'Component.tsx.hbs', - isDirectory: false, - }, - ]), - })); - - const newTemplateManager = new TemplateManager(); - const templates = await newTemplateManager.discoverTemplates( - '/mock/workspace/project1', - ); - - expect(templates).toHaveLength(1); - expect(templates[0].name).toBe('SharedComponent'); - expect(templates[0].path).toBe( - '/mock/workspace/templates/SharedComponent', - ); - }); - - it('should prioritize project templates over workspace templates', async () => { - // Mock FileSystemManager to return both project and workspace templates - const MockedFileSystemManager = - require('../../../managers/FileSystemManager').FileSystemManager; - MockedFileSystemManager.mockImplementation(() => ({ - folderExists: jest.fn().mockResolvedValue(true), // Both exist - listDirectories: jest - .fn() - .mockResolvedValueOnce(['Component']) // Project templates - .mockResolvedValueOnce(['Component', 'OtherComponent']), // Workspace templates - listFiles: jest - .fn() - .mockResolvedValueOnce(['project.tsx.hbs']) // Project Component files - .mockResolvedValueOnce(['workspace.tsx.hbs']) // Workspace Component files - .mockResolvedValueOnce(['other.tsx.hbs']), // OtherComponent files - readFile: jest.fn().mockResolvedValue('Hello {{name}}!'), - listFilesRecursive: jest.fn().mockResolvedValue([ - { - name: 'project.tsx.hbs', - path: 'project.tsx.hbs', - isDirectory: false, - }, - ]), - })); - - const newTemplateManager = new TemplateManager(); - const templates = await newTemplateManager.discoverTemplates( - '/mock/workspace/project1', - ); - - expect(templates).toHaveLength(2); - expect(templates[0].name).toBe('Component'); - expect(templates[0].files).toEqual(['project.tsx.hbs']); // Project version wins - expect(templates[1].name).toBe('OtherComponent'); - }); - - it('should cache template discovery results', async () => { - const templates1 = await templateManager.discoverTemplates( - '/mock/workspace/project1', - ); - const templates2 = await templateManager.discoverTemplates( - '/mock/workspace/project1', - ); - - expect(templates1).toEqual(templates2); // Should have same content - // Since we're using FileSystemManager mock, we can't easily test the fs calls - expect(templates1).toHaveLength(2); // Should return the mocked templates - }); - - it('should handle template discovery errors', async () => { - // Mock FileSystemManager to throw errors - const MockedFileSystemManager = - require('../../../managers/FileSystemManager').FileSystemManager; - MockedFileSystemManager.mockImplementation(() => ({ - folderExists: jest - .fn() - .mockRejectedValue(new Error('Permission denied')), - listDirectories: jest - .fn() - .mockRejectedValue(new Error('Permission denied')), - listFiles: jest.fn().mockRejectedValue(new Error('Permission denied')), - readFile: jest.fn().mockRejectedValue(new Error('Permission denied')), - listFilesRecursive: jest - .fn() - .mockRejectedValue(new Error('Permission denied')), - })); - - const newTemplateManager = new TemplateManager(); - const result = - await newTemplateManager.discoverTemplates('/invalid/path'); - expect(result).toEqual([]); - }); - }); - - describe('processTemplate', () => { - it('should compile and process template with component name', async () => { - const mockTemplate = jest.fn().mockReturnValue('processed content'); - mockHandlebars.compile.mockReturnValue(mockTemplate); - - // Mock FileSystemManager readFile method - const MockedFileSystemManager = - require('../../../managers/FileSystemManager').FileSystemManager; - MockedFileSystemManager.mockImplementation(() => ({ - folderExists: jest.fn().mockResolvedValue(true), - listDirectories: jest - .fn() - .mockResolvedValue(['ComponentSass', 'ComponentStyled']), - listFiles: jest - .fn() - .mockResolvedValue(['Component.tsx.hbs', 'index.tsx.hbs']), - readFile: jest.fn().mockResolvedValue('template content'), - listFilesRecursive: jest.fn().mockResolvedValue([ - { - name: 'Component.tsx.hbs', - path: 'Component.tsx.hbs', - isDirectory: false, - }, - { name: 'index.tsx.hbs', path: 'index.tsx.hbs', isDirectory: false }, - ]), - })); - - const newTemplateManager = new TemplateManager(); - const result = await newTemplateManager.processTemplate( - '/path/to/template.hbs', - 'myComponent', - ); - - expect(mockHandlebars.compile).toHaveBeenCalledWith('template content'); - expect(mockTemplate).toHaveBeenCalledWith({ name: 'Mycomponent' }); - expect(result).toBe('processed content'); - }); - - it('should format component name to PascalCase', async () => { - const mockTemplate = jest.fn().mockReturnValue('processed content'); - mockHandlebars.compile.mockReturnValue(mockTemplate); - - // Mock FileSystemManager readFile method - const MockedFileSystemManager = - require('../../../managers/FileSystemManager').FileSystemManager; - MockedFileSystemManager.mockImplementation(() => ({ - folderExists: jest.fn().mockResolvedValue(true), - listDirectories: jest - .fn() - .mockResolvedValue(['ComponentSass', 'ComponentStyled']), - listFiles: jest - .fn() - .mockResolvedValue(['Component.tsx.hbs', 'index.tsx.hbs']), - readFile: jest.fn().mockResolvedValue('template content'), - listFilesRecursive: jest.fn().mockResolvedValue([ - { - name: 'Component.tsx.hbs', - path: 'Component.tsx.hbs', - isDirectory: false, - }, - { name: 'index.tsx.hbs', path: 'index.tsx.hbs', isDirectory: false }, - ]), - })); - - const newTemplateManager = new TemplateManager(); - await newTemplateManager.processTemplate( - '/path/to/template.hbs', - 'my-component-name', - ); - - expect(mockTemplate).toHaveBeenCalledWith({ name: 'MyComponentName' }); - }); - - it('should cache compiled templates', async () => { - const mockTemplate = jest.fn().mockReturnValue('processed content'); - mockHandlebars.compile.mockReturnValue(mockTemplate); - - // Mock FileSystemManager readFile method - const MockedFileSystemManager = - require('../../../managers/FileSystemManager').FileSystemManager; - const mockReadFile = jest.fn().mockResolvedValue('template content'); - MockedFileSystemManager.mockImplementation(() => ({ - folderExists: jest.fn().mockResolvedValue(true), - listDirectories: jest - .fn() - .mockResolvedValue(['ComponentSass', 'ComponentStyled']), - listFiles: jest - .fn() - .mockResolvedValue(['Component.tsx.hbs', 'index.tsx.hbs']), - readFile: mockReadFile, - listFilesRecursive: jest.fn().mockResolvedValue([ - { - name: 'Component.tsx.hbs', - path: 'Component.tsx.hbs', - isDirectory: false, - }, - { name: 'index.tsx.hbs', path: 'index.tsx.hbs', isDirectory: false }, - ]), - })); - - const newTemplateManager = new TemplateManager(); - await newTemplateManager.processTemplate( - '/path/to/template.hbs', - 'component1', - ); - await newTemplateManager.processTemplate( - '/path/to/template.hbs', - 'component2', - ); - - // Template should be read and compiled only once (cached) - expect(mockReadFile).toHaveBeenCalledTimes(1); - expect(mockHandlebars.compile).toHaveBeenCalledTimes(1); // Should compile only once - expect(mockTemplate).toHaveBeenCalledTimes(2); // Should execute twice - }); - - it('should handle template processing errors', async () => { - // Mock FileSystemManager to throw errors - const MockedFileSystemManager = - require('../../../managers/FileSystemManager').FileSystemManager; - MockedFileSystemManager.mockImplementation(() => ({ - folderExists: jest.fn().mockResolvedValue(true), - listDirectories: jest.fn().mockResolvedValue([]), - listFiles: jest.fn().mockResolvedValue([]), - readFile: jest.fn().mockRejectedValue(new Error('File not found')), - listFilesRecursive: jest.fn().mockResolvedValue([]), - })); - - const newTemplateManager = new TemplateManager(); - await expect( - newTemplateManager.processTemplate( - '/invalid/template.hbs', - 'component', - ), - ).rejects.toThrow('Error processing template'); - }); - }); - - describe('getTemplateFiles', () => { - it('should return only .hbs files', () => { - mockFs.readdirSync.mockReturnValue([ - 'Component.tsx.hbs', - 'Component.scss.hbs', - 'index.tsx.hbs', - 'README.md', - 'package.json', - ]); - - const files = templateManager.getTemplateFiles('/path/to/template'); - - expect(files).toEqual([ - 'Component.tsx.hbs', - 'Component.scss.hbs', - 'index.tsx.hbs', - ]); - }); - - it('should handle directory read errors', () => { - const originalReaddirSync = mockFs.readdirSync; - mockFs.readdirSync.mockImplementation(() => { - throw new Error('Permission denied'); - }); - - try { - expect(() => - templateManager.getTemplateFiles('/invalid/path'), - ).toThrow(); - } finally { - mockFs.readdirSync = originalReaddirSync; - } - }); - }); - - describe('cache management', () => { - it('should invalidate all cache', async () => { - mockFs.promises.access.mockResolvedValue(undefined); - mockFs.promises.readdir.mockResolvedValue([]); - - await templateManager.discoverTemplates('/mock/workspace/project1'); - - const statsBefore = templateManager.getCacheStats(); - expect(statsBefore.templateCacheSize).toBeGreaterThan(0); - - templateManager.invalidateCache(); - - const statsAfter = templateManager.getCacheStats(); - expect(statsAfter.templateCacheSize).toBe(0); - expect(statsAfter.compiledCacheSize).toBe(0); - }); - - it('should invalidate project-specific cache', async () => { - // Mock FileSystemManager for this test - const MockedFileSystemManager = - require('../../../managers/FileSystemManager').FileSystemManager; - const mockFolderExists = jest.fn().mockResolvedValue(true); - const mockListDirectories = jest.fn().mockResolvedValue(['Component']); - - MockedFileSystemManager.mockImplementation(() => ({ - folderExists: mockFolderExists, - listDirectories: mockListDirectories, - listFiles: jest.fn().mockResolvedValue(['Component.tsx.hbs']), - readFile: jest.fn().mockResolvedValue('template content'), - listFilesRecursive: jest.fn().mockResolvedValue([ - { - name: 'Component.tsx.hbs', - path: 'Component.tsx.hbs', - isDirectory: false, - }, - ]), - })); - - const newTemplateManager = new TemplateManager(); - - await newTemplateManager.discoverTemplates('/mock/workspace/project1'); - await newTemplateManager.discoverTemplates('/mock/workspace/project2'); - - newTemplateManager.invalidateProjectCache('/mock/workspace/project1'); - - // Should re-read project1 but not project2 - await newTemplateManager.discoverTemplates('/mock/workspace/project1'); - await newTemplateManager.discoverTemplates('/mock/workspace/project2'); - - // Each discoverTemplates call should check folderExists for project and workspace paths - expect(mockFolderExists).toHaveBeenCalledTimes(6); // 2 + 2 + 2 (each call tries project and workspace) - }); - - it('should clean expired cache entries', async () => { - // Mock Date.now to simulate time passing - const originalNow = Date.now; - let mockTime = 1000000; - Date.now = jest.fn(() => mockTime); - - try { - mockFs.promises.access.mockResolvedValue(undefined); - mockFs.promises.readdir.mockResolvedValue([]); - - await templateManager.discoverTemplates('/mock/workspace/project1'); - - // Advance time beyond cache TTL (5 minutes) - mockTime += 6 * 60 * 1000; - - templateManager.cleanExpiredCache(); - - const stats = templateManager.getCacheStats(); - expect(stats.templateCacheSize).toBe(0); - } finally { - Date.now = originalNow; - } - }); - }); - - describe('preloadTemplates', () => { - it('should preload templates for multiple projects', async () => { - mockFs.promises.access.mockResolvedValue(undefined); - mockFs.promises.readdir.mockResolvedValue([]); - - await templateManager.preloadTemplates([ - '/mock/workspace/project1', - '/mock/workspace/project2', - ]); - - const stats = templateManager.getCacheStats(); - expect(stats.templateCacheSize).toBe(2); - }); - - it('should handle preload errors gracefully', async () => { - mockFs.promises.access.mockRejectedValue(new Error('Permission denied')); - - // Should not throw error - await expect( - templateManager.preloadTemplates(['/invalid/path']), - ).resolves.toBeUndefined(); - }); - }); - - describe('template validation', () => { - describe('validateProcessedTemplate', () => { - it('should pass validation when no duplicate target paths exist', async () => { - // Mock DirectoryProcessor to return a simple structure - const MockedDirectoryProcessor = - require('../../../utils/DirectoryProcessor').DirectoryProcessor; - MockedDirectoryProcessor.mockImplementation(() => ({ - scanTemplateStructure: jest.fn().mockResolvedValue({ - rootPath: '/test/templates/ComponentTemplate', - files: [ - { - name: 'Component.tsx.hbs', - path: '/test/templates/ComponentTemplate/Component.tsx.hbs', - relativePath: 'Component.tsx.hbs', - }, - { - name: 'index.tsx.hbs', - path: '/test/templates/ComponentTemplate/index.tsx.hbs', - relativePath: 'index.tsx.hbs', - }, - ], - directories: [], - }), - validateDepth: jest.fn(), - })); - - const newTemplateManager = new TemplateManager(); - const templateStructure = - await newTemplateManager.scanTemplateStructure( - '/test/templates/ComponentTemplate', - ); - - // This should not throw an error - await expect( - newTemplateManager.processTemplateHierarchy( - templateStructure, - 'TestComponent', - ), - ).resolves.toBeDefined(); - }); - - it('should throw error when duplicate target paths are detected', async () => { - // Create a mock processed template with duplicate target paths directly - const processedTemplate = { - componentName: 'TestComponent', - files: [ - { - originalPath: '/test/Component.tsx.hbs', - targetPath: 'Component.tsx', - content: 'content1', - }, - ], - directories: [ - { - originalPath: '/test/src', - targetPath: 'src', - files: [ - { - originalPath: '/test/src/Component.tsx.hbs', - targetPath: 'Component.tsx', - content: 'content2', - }, - ], - subdirectories: [], - }, - ], - }; - - // Test the validation method directly - expect(() => { - ( - templateManager as unknown as TemplateManagerTestable - ).validateProcessedTemplate(processedTemplate); - }).toThrow('Duplicate target paths detected: Component.tsx'); - }); - - it('should handle nested directory structures correctly', async () => { - // Mock DirectoryProcessor to return a nested structure - const MockedDirectoryProcessor = - require('../../../utils/DirectoryProcessor').DirectoryProcessor; - MockedDirectoryProcessor.mockImplementation(() => ({ - scanTemplateStructure: jest.fn().mockResolvedValue({ - rootPath: '/test/templates/ComponentTemplate', - files: [ - { - name: 'index.tsx.hbs', - path: '/test/templates/ComponentTemplate/index.tsx.hbs', - relativePath: 'index.tsx.hbs', - }, - ], - directories: [ - { - name: 'src', - path: '/test/templates/ComponentTemplate/src', - relativePath: 'src', - files: [ - { - name: 'Component.tsx.hbs', - path: '/test/templates/ComponentTemplate/src/Component.tsx.hbs', - relativePath: 'Component.tsx.hbs', - }, - ], - subdirectories: [ - { - name: 'lib', - path: '/test/templates/ComponentTemplate/src/lib', - relativePath: 'lib', - files: [ - { - name: 'main.tsx.hbs', - path: '/test/templates/ComponentTemplate/src/lib/main.tsx.hbs', - relativePath: 'main.tsx.hbs', - }, - ], - subdirectories: [], - }, - ], - }, - ], - }), - validateDepth: jest.fn(), - })); - - const newTemplateManager = new TemplateManager(); - const templateStructure = - await newTemplateManager.scanTemplateStructure( - '/test/templates/ComponentTemplate', - ); - - // This should not throw an error - all paths should be unique - const result = await newTemplateManager.processTemplateHierarchy( - templateStructure, - 'TestComponent', - ); - - expect(result).toBeDefined(); - expect(result.files).toHaveLength(1); - expect(result.directories).toHaveLength(1); - expect(result.directories[0].files).toHaveLength(1); - expect(result.directories[0].subdirectories).toHaveLength(1); - expect(result.directories[0].subdirectories[0].files).toHaveLength(1); - }); - }); - - describe('collectAllTargetPaths', () => { - it('should collect paths from root files only', () => { - const processedTemplate = { - componentName: 'TestComponent', - files: [ - { - originalPath: '/test/Component.tsx.hbs', - targetPath: 'Component.tsx', - content: 'content1', - }, - { - originalPath: '/test/index.tsx.hbs', - targetPath: 'index.tsx', - content: 'content2', - }, - ], - directories: [], - }; - - // Access the private method through type assertion - const paths = ( - templateManager as unknown as TemplateManagerTestable - ).collectAllTargetPaths(processedTemplate); - - expect(paths).toEqual(['Component.tsx', 'index.tsx']); - }); - - it('should collect paths from nested directories', () => { - const processedTemplate = { - componentName: 'TestComponent', - files: [ - { - originalPath: '/test/index.tsx.hbs', - targetPath: 'index.tsx', - content: 'content1', - }, - ], - directories: [ - { - originalPath: '/test/src', - targetPath: 'src', - files: [ - { - originalPath: '/test/src/Component.tsx.hbs', - targetPath: 'src/Component.tsx', - content: 'content2', - }, - ], - subdirectories: [ - { - originalPath: '/test/src/lib', - targetPath: 'src/lib', - files: [ - { - originalPath: '/test/src/lib/main.tsx.hbs', - targetPath: 'src/lib/main.tsx', - content: 'content3', - }, - ], - subdirectories: [], - }, - ], - }, - ], - }; - - // Access the private method through type assertion - const paths = ( - templateManager as unknown as TemplateManagerTestable - ).collectAllTargetPaths(processedTemplate); - - expect(paths).toEqual([ - 'index.tsx', - 'src/Component.tsx', - 'src/lib/main.tsx', - ]); - }); - }); - - describe('findDuplicatePaths', () => { - it('should return empty array when no duplicates exist', () => { - const paths = ['Component.tsx', 'index.tsx', 'src/main.tsx']; - - // Access the private method through type assertion - const duplicates = ( - templateManager as unknown as TemplateManagerTestable - ).findDuplicatePaths(paths); - - expect(duplicates).toEqual([]); - }); - - it('should find duplicate paths', () => { - const paths = [ - 'Component.tsx', - 'index.tsx', - 'Component.tsx', - 'src/main.tsx', - 'index.tsx', - ]; - - // Access the private method through type assertion - const duplicates = ( - templateManager as unknown as TemplateManagerTestable - ).findDuplicatePaths(paths); - - expect(duplicates).toEqual(['Component.tsx', 'index.tsx']); - }); - - it('should handle empty array', () => { - const paths: string[] = []; - - // Access the private method through type assertion - const duplicates = ( - templateManager as unknown as TemplateManagerTestable - ).findDuplicatePaths(paths); - - expect(duplicates).toEqual([]); - }); - }); - }); -}); diff --git a/src/test/unit/managers/TemplateManagerNaming.test.ts b/src/test/unit/managers/TemplateManagerNaming.test.ts deleted file mode 100644 index 4905800..0000000 --- a/src/test/unit/managers/TemplateManagerNaming.test.ts +++ /dev/null @@ -1,237 +0,0 @@ -import { TemplateManager } from '../../../managers/TemplateManager'; - -type TemplateManagerTestable = { - applyNamingRules( - filePath: string, - componentName: string, - templateFolderName?: string, - ): string; - extractTemplateFolderName(templateRootPath: string): string | undefined; -}; - -// Mock FileSystemManager -jest.mock('../../../managers/FileSystemManager', () => ({ - FileSystemManager: jest.fn().mockImplementation(() => ({ - folderExists: jest.fn().mockResolvedValue(true), - listDirectories: jest.fn().mockResolvedValue([]), - listFiles: jest.fn().mockResolvedValue([]), - readFile: jest.fn().mockResolvedValue('Hello {{name}}!'), - listFilesRecursive: jest.fn().mockResolvedValue([]), - })), -})); - -// Mock DirectoryProcessor -jest.mock('../../../utils/DirectoryProcessor', () => ({ - DirectoryProcessor: jest.fn().mockImplementation(() => ({ - scanTemplateStructure: jest.fn().mockResolvedValue({ - rootPath: '/test/templates/ComponentSass', - files: [], - directories: [], - }), - validateDepth: jest.fn(), - })), -})); - -describe('TemplateManager - New Naming Rules', () => { - let templateManager: TemplateManager; - - beforeEach(() => { - jest.clearAllMocks(); - templateManager = new TemplateManager(); - }); - - describe('applyNamingRules with template folder name', () => { - it('should replace file name starting with template folder name', () => { - // Access the private method for testing - const applyNamingRules = ( - templateManager as unknown as TemplateManagerTestable - ).applyNamingRules.bind(templateManager); - - const result = applyNamingRules( - 'ComponentSass.stories.tsx', - 'Page', - 'ComponentSass', - ); - expect(result).toBe('Page.stories.tsx'); - }); - - it('should replace file name with different component name', () => { - const applyNamingRules = ( - templateManager as unknown as TemplateManagerTestable - ).applyNamingRules.bind(templateManager); - - const result = applyNamingRules( - 'ComponentSass.module.scss', - 'UserProfile', - 'ComponentSass', - ); - expect(result).toBe('UserProfile.module.scss'); - }); - - it('should handle case insensitive matching', () => { - const applyNamingRules = ( - templateManager as unknown as TemplateManagerTestable - ).applyNamingRules.bind(templateManager); - - const result = applyNamingRules( - 'componentsass.test.tsx', - 'Page', - 'ComponentSass', - ); - expect(result).toBe('Page.test.tsx'); - }); - - it('should not change files that do not start with template folder name', () => { - const applyNamingRules = ( - templateManager as unknown as TemplateManagerTestable - ).applyNamingRules.bind(templateManager); - - const result = applyNamingRules('index.tsx', 'Page', 'ComponentSass'); - expect(result).toBe('index.tsx'); - }); - - it('should preserve existing template/Template replacement functionality', () => { - const applyNamingRules = ( - templateManager as unknown as TemplateManagerTestable - ).applyNamingRules.bind(templateManager); - - const result = applyNamingRules( - 'template.config.js', - 'Page', - 'ComponentSass', - ); - expect(result).toBe('page.config.js'); - }); - - it('should handle both template folder name and template keyword', () => { - const applyNamingRules = ( - templateManager as unknown as TemplateManagerTestable - ).applyNamingRules.bind(templateManager); - - // File starts with template folder name AND contains template keyword - const result = applyNamingRules( - 'ComponentSass.template.tsx', - 'Page', - 'ComponentSass', - ); - expect(result).toBe('Page.page.tsx'); - }); - - it('should work without template folder name (fallback to existing behavior)', () => { - const applyNamingRules = ( - templateManager as unknown as TemplateManagerTestable - ).applyNamingRules.bind(templateManager); - - const result = applyNamingRules('ComponentSass.stories.tsx', 'Page'); - expect(result).toBe('ComponentSass.stories.tsx'); - }); - - it('should handle empty or undefined template folder name', () => { - const applyNamingRules = ( - templateManager as unknown as TemplateManagerTestable - ).applyNamingRules.bind(templateManager); - - const result1 = applyNamingRules('ComponentSass.stories.tsx', 'Page', ''); - expect(result1).toBe('ComponentSass.stories.tsx'); - - const result2 = applyNamingRules( - 'ComponentSass.stories.tsx', - 'Page', - undefined, - ); - expect(result2).toBe('ComponentSass.stories.tsx'); - }); - }); - - describe('extractTemplateFolderName', () => { - it('should extract folder name from Unix path', () => { - const extractTemplateFolderName = ( - templateManager as unknown as TemplateManagerTestable - ).extractTemplateFolderName.bind(templateManager); - - const result = extractTemplateFolderName( - '/path/to/templates/ComponentSass', - ); - expect(result).toBe('ComponentSass'); - }); - - it('should extract folder name from Windows path', () => { - const extractTemplateFolderName = ( - templateManager as unknown as TemplateManagerTestable - ).extractTemplateFolderName.bind(templateManager); - - const result = extractTemplateFolderName( - 'C:\\templates\\ComponentStyled', - ); - expect(result).toBe('ComponentStyled'); - }); - - it('should extract folder name from relative path', () => { - const extractTemplateFolderName = ( - templateManager as unknown as TemplateManagerTestable - ).extractTemplateFolderName.bind(templateManager); - - const result = extractTemplateFolderName('ComponentSass'); - expect(result).toBe('ComponentSass'); - }); - - it('should return undefined for invalid paths', () => { - const extractTemplateFolderName = ( - templateManager as unknown as TemplateManagerTestable - ).extractTemplateFolderName.bind(templateManager); - - const result1 = extractTemplateFolderName(''); - expect(result1).toBeUndefined(); - - const result2 = extractTemplateFolderName('.'); - expect(result2).toBeUndefined(); - - const result3 = extractTemplateFolderName('..'); - expect(result3).toBeUndefined(); - }); - - it('should handle errors gracefully', () => { - const extractTemplateFolderName = ( - templateManager as unknown as TemplateManagerTestable - ).extractTemplateFolderName.bind(templateManager); - - // This should not throw an error - const result = extractTemplateFolderName(null as unknown as string); - expect(result).toBeUndefined(); - }); - }); - - describe('integration with processTemplateHierarchy', () => { - it('should extract template folder name and pass it through the processing chain', async () => { - // Mock DirectoryProcessor to return a structure with files - const MockedDirectoryProcessor = - require('../../../utils/DirectoryProcessor').DirectoryProcessor; - MockedDirectoryProcessor.mockImplementation(() => ({ - scanTemplateStructure: jest.fn().mockResolvedValue({ - rootPath: '/test/templates/ComponentSass', - files: [ - { - name: 'ComponentSass.stories.tsx.hbs', - path: '/test/templates/ComponentSass/ComponentSass.stories.tsx.hbs', - relativePath: 'ComponentSass.stories.tsx.hbs', - }, - ], - directories: [], - }), - validateDepth: jest.fn(), - })); - - const newTemplateManager = new TemplateManager(); - const templateStructure = await newTemplateManager.scanTemplateStructure( - '/test/templates/ComponentSass', - ); - const result = await newTemplateManager.processTemplateHierarchy( - templateStructure, - 'Page', - ); - - expect(result.files).toHaveLength(1); - expect(result.files[0].targetPath).toBe('Page.stories.tsx'); - }); - }); -}); diff --git a/src/test/unit/utils/PathResolver.integration.test.ts b/src/test/unit/utils/PathResolver.integration.test.ts deleted file mode 100644 index 314f917..0000000 --- a/src/test/unit/utils/PathResolver.integration.test.ts +++ /dev/null @@ -1,298 +0,0 @@ -import type { ProjectContext } from '../../../types'; -import { PathResolver } from '../../../utils/PathResolver'; - -describe('PathResolver Integration Tests', () => { - let pathResolver: PathResolver; - - beforeEach(() => { - pathResolver = new PathResolver(); - }); - - describe('Multi-project workspace scenarios', () => { - it('should resolve paths correctly for nested project structure', () => { - const nestedProjectContext: ProjectContext = { - workspaceRoot: '/workspace', - projectRoot: '/workspace/apps/frontend', - templatePath: '/workspace/apps/frontend/templates', - configPath: '/workspace/apps/frontend/codebot.config.json', - isMultiProject: true, - projectName: 'frontend', - }; - - const templatePath = pathResolver.resolveTemplatePath( - nestedProjectContext, - 'ReactComponent', - ); - expect(templatePath).toBe( - '/workspace/apps/frontend/templates/ReactComponent', - ); - - const targetPath = pathResolver.resolveTargetPath( - nestedProjectContext, - 'MyComponent', - ); - expect(targetPath).toBe('/workspace/apps/frontend/MyComponent'); - - const configPath = pathResolver.resolveConfigPath(nestedProjectContext); - expect(configPath).toBe('/workspace/apps/frontend/codebot.config.json'); - }); - - it('should resolve paths correctly for monorepo with packages', () => { - const packageProjectContext: ProjectContext = { - workspaceRoot: '/monorepo', - projectRoot: '/monorepo/packages/ui-components', - templatePath: '/monorepo/packages/ui-components/templates', - configPath: '/monorepo/packages/ui-components/codebot.config.json', - isMultiProject: true, - projectName: 'ui-components', - }; - - const templatePath = pathResolver.resolveTemplatePath( - packageProjectContext, - 'Component', - ); - expect(templatePath).toBe( - '/monorepo/packages/ui-components/templates/Component', - ); - - const relativePath = pathResolver.resolveRelativePath( - packageProjectContext, - 'src/components', - ); - expect(relativePath).toBe( - '/monorepo/packages/ui-components/src/components', - ); - }); - - it('should handle deeply nested project structures', () => { - const deepProjectContext: ProjectContext = { - workspaceRoot: '/company', - projectRoot: '/company/projects/web/apps/admin/modules/user-management', - templatePath: - '/company/projects/web/apps/admin/modules/user-management/templates', - configPath: - '/company/projects/web/apps/admin/modules/user-management/codebot.config.json', - isMultiProject: true, - projectName: 'user-management', - }; - - const targetPath = pathResolver.resolveTargetPath( - deepProjectContext, - 'UserProfile', - ); - expect(targetPath).toBe( - '/company/projects/web/apps/admin/modules/user-management/UserProfile', - ); - - // Ensure boundary validation works with deep paths - expect(() => { - pathResolver.resolveRelativePath( - deepProjectContext, - '../../../../../malicious', - ); - }).toThrow(); - }); - }); - - describe('Single project workspace scenarios', () => { - it('should resolve paths correctly for simple single project', () => { - const singleProjectContext: ProjectContext = { - workspaceRoot: '/simple-app', - projectRoot: '/simple-app', - templatePath: '/simple-app/templates', - configPath: '/simple-app/codebot.config.json', - isMultiProject: false, - }; - - const templatePath = pathResolver.resolveTemplatePath( - singleProjectContext, - 'Component', - ); - expect(templatePath).toBe('/simple-app/templates/Component'); - - const targetPath = pathResolver.resolveTargetPath( - singleProjectContext, - 'MyComponent', - ); - expect(targetPath).toBe('/simple-app/MyComponent'); - }); - - it('should handle single project with complex folder structure', () => { - const complexSingleProject: ProjectContext = { - workspaceRoot: '/complex-app', - projectRoot: '/complex-app', - templatePath: '/complex-app/templates', - configPath: '/complex-app/codebot.config.json', - isMultiProject: false, - }; - - const relativePath = pathResolver.resolveRelativePath( - complexSingleProject, - 'src/features/auth/components', - ); - expect(relativePath).toBe('/complex-app/src/features/auth/components'); - }); - }); - - describe('Edge cases and security', () => { - it('should prevent path traversal across project boundaries', () => { - const restrictedContext: ProjectContext = { - workspaceRoot: '/workspace', - projectRoot: '/workspace/project-a', - templatePath: '/workspace/project-a/templates', - configPath: '/workspace/project-a/codebot.config.json', - isMultiProject: true, - projectName: 'project-a', - }; - - // These should all throw errors due to path traversal attempts - expect(() => { - pathResolver.resolveRelativePath( - restrictedContext, - '../project-b/secrets', - ); - }).toThrow(); - - expect(() => { - pathResolver.resolveRelativePath(restrictedContext, '../../etc/passwd'); - }).toThrow(); - - expect(() => { - pathResolver.resolveTargetPath(restrictedContext, '../../../malicious'); - }).toThrow(); - }); - - it('should handle Windows-style paths in multi-project setup', () => { - const windowsContext: ProjectContext = { - workspaceRoot: 'C:\\workspace', - projectRoot: 'C:\\workspace\\project1', - templatePath: 'C:\\workspace\\project1\\templates', - configPath: 'C:\\workspace\\project1\\codebot.config.json', - isMultiProject: true, - projectName: 'project1', - }; - - const targetPath = pathResolver.resolveTargetPath( - windowsContext, - 'Component', - ); - expect(targetPath).toContain('Component'); - expect(targetPath).toContain('project1'); - }); - - it('should sanitize component names while preserving project structure', () => { - const projectContext: ProjectContext = { - workspaceRoot: '/workspace', - projectRoot: '/workspace/frontend', - templatePath: '/workspace/frontend/templates', - configPath: '/workspace/frontend/codebot.config.json', - isMultiProject: true, - projectName: 'frontend', - }; - - const targetPath = pathResolver.resolveTargetPath( - projectContext, - 'MyName', - ); - expect(targetPath).toBe('/workspace/frontend/My_Component_Name'); - - const templatePath = pathResolver.resolveTemplatePath( - projectContext, - 'React|Component', - ); - expect(templatePath).toBe( - '/workspace/frontend/templates/React_Component', - ); - }); - - it('should handle Unicode characters in project paths', () => { - const unicodeContext: ProjectContext = { - workspaceRoot: '/workspace', - projectRoot: '/workspace/项目-α', - templatePath: '/workspace/项目-α/templates', - configPath: '/workspace/项目-α/codebot.config.json', - isMultiProject: true, - projectName: '项目-α', - }; - - const targetPath = pathResolver.resolveTargetPath( - unicodeContext, - 'Component测试', - ); - expect(targetPath).toBe('/workspace/项目-α/Component测试'); - }); - }); - - describe('Performance and limits', () => { - it('should handle reasonable number of nested folders', () => { - const deepContext: ProjectContext = { - workspaceRoot: '/workspace', - projectRoot: '/workspace/project', - templatePath: '/workspace/project/templates', - configPath: '/workspace/project/codebot.config.json', - isMultiProject: false, - }; - - const deepPath = - 'src/features/auth/components/forms/inputs/text/validation/rules'; - const resolvedPath = pathResolver.resolveRelativePath( - deepContext, - deepPath, - ); - expect(resolvedPath).toBe( - '/workspace/project/src/features/auth/components/forms/inputs/text/validation/rules', - ); - }); - - it('should reject excessively long component names', () => { - const context: ProjectContext = { - workspaceRoot: '/workspace', - projectRoot: '/workspace/project', - templatePath: '/workspace/project/templates', - configPath: '/workspace/project/codebot.config.json', - isMultiProject: false, - }; - - const veryLongName = `Component${'A'.repeat(250)}`; // 259 characters - expect(() => { - pathResolver.resolveTargetPath(context, veryLongName); - }).toThrow(); - }); - }); - - describe('Cross-platform compatibility', () => { - it('should normalize paths consistently across platforms', () => { - const mixedSeparatorContext: ProjectContext = { - workspaceRoot: '/workspace', - projectRoot: '/workspace/project', - templatePath: '/workspace/project/templates', - configPath: '/workspace/project/codebot.config.json', - isMultiProject: false, - }; - - // Test mixed separators - const mixedPath = pathResolver.resolveRelativePath( - mixedSeparatorContext, - 'src\\components/forms', - ); - expect(mixedPath).toBe('/workspace/project/src/components/forms'); - }); - - it('should handle case sensitivity appropriately', () => { - const context: ProjectContext = { - workspaceRoot: '/workspace', - projectRoot: '/workspace/Project', - templatePath: '/workspace/Project/templates', - configPath: '/workspace/Project/codebot.config.json', - isMultiProject: false, - }; - - const upperPath = pathResolver.resolveTargetPath(context, 'COMPONENT'); - const lowerPath = pathResolver.resolveTargetPath(context, 'component'); - - expect(upperPath).not.toBe(lowerPath); - expect(upperPath).toContain('COMPONENT'); - expect(lowerPath).toContain('component'); - }); - }); -}); diff --git a/src/test/unit/utils/PathResolver.test.ts b/src/test/unit/utils/PathResolver.test.ts deleted file mode 100644 index 70ca8b2..0000000 --- a/src/test/unit/utils/PathResolver.test.ts +++ /dev/null @@ -1,382 +0,0 @@ -import type { ProjectContext } from '../../../types'; -import { PathResolver } from '../../../utils/PathResolver'; -import { mockProjectContext, mockSingleProjectContext } from '../../fixtures'; - -describe('PathResolver', () => { - let pathResolver: PathResolver; - let mockMultiProjectContext: ProjectContext; - let mockSingleProjectContextLocal: ProjectContext; - - beforeEach(() => { - pathResolver = new PathResolver(); - - // Create test-specific contexts to avoid modifying shared fixtures - mockMultiProjectContext = { - ...mockProjectContext, - workspaceRoot: '/workspace', - projectRoot: '/workspace/project1', - templatePath: '/workspace/project1/templates', - configPath: '/workspace/project1/codebot.config.json', - isMultiProject: true, - projectName: 'project1', - }; - - mockSingleProjectContextLocal = { - ...mockSingleProjectContext, - workspaceRoot: '/workspace', - projectRoot: '/workspace', - templatePath: '/workspace/templates', - configPath: '/workspace/codebot.config.json', - isMultiProject: false, - }; - }); - - describe('validatePath', () => { - it('should validate correct paths', () => { - expect(pathResolver.validatePath('component-name')).toBe(true); - expect(pathResolver.validatePath('ComponentName')).toBe(true); - expect(pathResolver.validatePath('component_name')).toBe(true); - expect(pathResolver.validatePath('folder/component')).toBe(true); - }); - - it('should reject paths with parent directory traversal', () => { - expect(pathResolver.validatePath('../component')).toBe(false); - expect(pathResolver.validatePath('folder/../component')).toBe(false); - expect(pathResolver.validatePath('../../malicious')).toBe(false); - }); - - it('should reject absolute paths', () => { - expect(pathResolver.validatePath('/absolute/path')).toBe(false); - expect(pathResolver.validatePath('C:\\Windows\\System32')).toBe(false); - }); - - it('should reject paths with invalid characters', () => { - expect(pathResolver.validatePath('componentname')).toBe(false); - expect(pathResolver.validatePath('component:name')).toBe(false); - expect(pathResolver.validatePath('component"name')).toBe(false); - expect(pathResolver.validatePath('component|name')).toBe(false); - expect(pathResolver.validatePath('component?name')).toBe(false); - expect(pathResolver.validatePath('component*name')).toBe(false); - }); - - it('should reject paths with null bytes', () => { - expect(pathResolver.validatePath('component\0name')).toBe(false); - }); - - it('should reject reserved Windows names', () => { - expect(pathResolver.validatePath('CON')).toBe(false); - expect(pathResolver.validatePath('PRN')).toBe(false); - expect(pathResolver.validatePath('AUX')).toBe(false); - expect(pathResolver.validatePath('NUL')).toBe(false); - expect(pathResolver.validatePath('COM1')).toBe(false); - expect(pathResolver.validatePath('LPT1')).toBe(false); - }); - - it('should reject empty or null paths', () => { - expect(pathResolver.validatePath('')).toBe(false); - expect(pathResolver.validatePath(null as unknown as string)).toBe(false); - expect(pathResolver.validatePath(undefined as unknown as string)).toBe( - false, - ); - }); - - it('should reject paths that are too long', () => { - const longPath = 'a'.repeat(300); - expect(pathResolver.validatePath(longPath)).toBe(false); - }); - - it('should reject paths with empty segments', () => { - expect(pathResolver.validatePath('folder//component')).toBe(false); - expect(pathResolver.validatePath('folder\\\\')).toBe(false); - }); - }); - - describe('sanitizePath', () => { - it('should sanitize dangerous characters', () => { - expect(pathResolver.sanitizePath('componentname')).toBe( - 'component_name', - ); - expect(pathResolver.sanitizePath('component:name')).toBe( - 'component_name', - ); - expect(pathResolver.sanitizePath('component"name')).toBe( - 'component_name', - ); - expect(pathResolver.sanitizePath('component|name')).toBe( - 'component_name', - ); - expect(pathResolver.sanitizePath('component?name')).toBe( - 'component_name', - ); - expect(pathResolver.sanitizePath('component*name')).toBe( - 'component_name', - ); - }); - - it('should remove null bytes', () => { - expect(pathResolver.sanitizePath('component\0name')).toBe( - 'componentname', - ); - }); - - it('should remove parent directory references', () => { - expect(pathResolver.sanitizePath('../component')).toBe('component'); - expect(pathResolver.sanitizePath('folder/../component')).toBe( - 'folder/component', - ); - expect(pathResolver.sanitizePath('../../malicious')).toBe('malicious'); - }); - - it('should normalize path separators', () => { - expect(pathResolver.sanitizePath('folder\\component')).toBe( - 'folder/component', - ); - expect(pathResolver.sanitizePath('folder//component')).toBe( - 'folder/component', - ); - }); - - it('should handle reserved names by appending underscore', () => { - expect(pathResolver.sanitizePath('CON')).toBe('CON_'); - expect(pathResolver.sanitizePath('PRN')).toBe('PRN_'); - expect(pathResolver.sanitizePath('folder/AUX')).toBe('folder/AUX_'); - }); - - it('should trim whitespace and dots', () => { - expect(pathResolver.sanitizePath(' component ')).toBe('component'); - expect(pathResolver.sanitizePath('..component..')).toBe('component'); - }); - - it('should handle empty or null input', () => { - expect(pathResolver.sanitizePath('')).toBe(''); - expect(pathResolver.sanitizePath(null as unknown as string)).toBe(''); - expect(pathResolver.sanitizePath(undefined as unknown as string)).toBe( - '', - ); - }); - }); - - describe('resolveTemplatePath', () => { - it('should resolve template path for multi-project workspace', () => { - const result = pathResolver.resolveTemplatePath( - mockMultiProjectContext, - 'ComponentSass', - ); - expect(result).toBe('/workspace/project1/templates/ComponentSass'); - }); - - it('should resolve template path for single project workspace', () => { - const result = pathResolver.resolveTemplatePath( - mockSingleProjectContextLocal, - 'ComponentSass', - ); - expect(result).toBe('/workspace/templates/ComponentSass'); - }); - - it('should sanitize template name', () => { - const result = pathResolver.resolveTemplatePath( - mockMultiProjectContext, - 'Component', - ); - expect(result).toBe('/workspace/project1/templates/Component_Sass_'); - }); - - it('should throw error for invalid template name', () => { - expect(() => { - pathResolver.resolveTemplatePath( - mockMultiProjectContext, - '../malicious', - ); - }).toThrow(); - }); - }); - - describe('resolveTargetPath', () => { - it('should resolve target path for multi-project workspace', () => { - const result = pathResolver.resolveTargetPath( - mockMultiProjectContext, - 'MyComponent', - ); - expect(result).toBe('/workspace/project1/MyComponent'); - }); - - it('should resolve target path for single project workspace', () => { - const result = pathResolver.resolveTargetPath( - mockSingleProjectContextLocal, - 'MyComponent', - ); - expect(result).toBe('/workspace/MyComponent'); - }); - - it('should sanitize component name', () => { - const result = pathResolver.resolveTargetPath( - mockMultiProjectContext, - 'My', - ); - expect(result).toBe('/workspace/project1/My_Component_'); - }); - - it('should throw error for invalid component name', () => { - expect(() => { - pathResolver.resolveTargetPath(mockMultiProjectContext, '../malicious'); - }).toThrow(); - }); - - it('should throw error if target path is outside project boundaries', () => { - // Mock a scenario where sanitized path could still be problematic - const maliciousContext = { - ...mockMultiProjectContext, - projectRoot: '/workspace/project1', - }; - - expect(() => { - // This should be caught by boundary validation - pathResolver.resolveTargetPath(maliciousContext, 'legitimate-name'); - }).not.toThrow(); // This should work fine - }); - }); - - describe('resolveConfigPath', () => { - it('should resolve config path for multi-project workspace', () => { - const result = pathResolver.resolveConfigPath(mockMultiProjectContext); - expect(result).toBe('/workspace/project1/codebot.config.json'); - }); - - it('should resolve config path for single project workspace', () => { - const result = pathResolver.resolveConfigPath( - mockSingleProjectContextLocal, - ); - expect(result).toBe('/workspace/codebot.config.json'); - }); - }); - - describe('resolveRelativePath', () => { - it('should resolve relative path within project boundaries', () => { - const result = pathResolver.resolveRelativePath( - mockMultiProjectContext, - 'src/components', - ); - expect(result).toBe('/workspace/project1/src/components'); - }); - - it('should sanitize relative path', () => { - const result = pathResolver.resolveRelativePath( - mockMultiProjectContext, - 'src/comp', - ); - expect(result).toBe('/workspace/project1/src/comp_onents_'); - }); - - it('should throw error for invalid relative path', () => { - expect(() => { - pathResolver.resolveRelativePath( - mockMultiProjectContext, - '../../../malicious', - ); - }).toThrow(); - }); - - it('should throw error if resolved path is outside boundaries', () => { - expect(() => { - pathResolver.resolveRelativePath( - mockMultiProjectContext, - '../outside-project', - ); - }).toThrow(); - }); - }); - - describe('edge cases and security', () => { - it('should handle Windows-style paths correctly', () => { - const windowsContext = { - ...mockMultiProjectContext, - workspaceRoot: 'C:\\workspace', - projectRoot: 'C:\\workspace\\project1', - }; - - const result = pathResolver.resolveTargetPath( - windowsContext, - 'MyComponent', - ); - expect(result).toContain('MyComponent'); - }); - - it('should handle very long component names', () => { - const longName = `Component${'A'.repeat(200)}`; - expect(() => { - pathResolver.resolveTargetPath(mockMultiProjectContext, longName); - }).toThrow(); - }); - - it('should handle special Unicode characters', () => { - const unicodeName = 'Component测试'; - const result = pathResolver.resolveTargetPath( - mockMultiProjectContext, - unicodeName, - ); - expect(result).toContain('Component测试'); - }); - - it('should prevent path traversal in nested folders', () => { - expect(() => { - pathResolver.resolveRelativePath( - mockMultiProjectContext, - 'folder/../../../etc/passwd', - ); - }).toThrow(); - }); - - it('should handle case sensitivity correctly', () => { - const result1 = pathResolver.resolveTargetPath( - mockMultiProjectContext, - 'component', - ); - const result2 = pathResolver.resolveTargetPath( - mockMultiProjectContext, - 'Component', - ); - expect(result1).not.toBe(result2); - }); - }); - - describe('boundary validation', () => { - it('should allow paths within project root', () => { - const context = { - ...mockMultiProjectContext, - projectRoot: '/workspace/project1', - workspaceRoot: '/workspace', - }; - - expect(() => { - pathResolver.resolveTargetPath(context, 'ValidComponent'); - }).not.toThrow(); - }); - - it('should allow paths within workspace root for single project', () => { - expect(() => { - pathResolver.resolveTargetPath( - mockSingleProjectContextLocal, - 'ValidComponent', - ); - }).not.toThrow(); - }); - - it('should reject paths that escape project boundaries', () => { - // Create a context where we can test boundary violations - const restrictedContext = { - ...mockMultiProjectContext, - projectRoot: '/workspace/project1', - }; - - // The PathResolver should prevent any path that goes outside the project - // This is more of an integration test with the boundary checking logic - expect(() => { - pathResolver.resolveRelativePath(restrictedContext, '../../../etc'); - }).toThrow(); - }); - }); -}); diff --git a/src/test/utils/DirectoryProcessor.test.ts b/src/test/utils/DirectoryProcessor.test.ts deleted file mode 100644 index 98b27bb..0000000 --- a/src/test/utils/DirectoryProcessor.test.ts +++ /dev/null @@ -1,244 +0,0 @@ -import * as fs from 'node:fs'; -import * as os from 'node:os'; -import * as path from 'node:path'; -import { FileSystemManager } from '../../managers/FileSystemManager'; -import { ErrorType } from '../../types'; -import { DirectoryProcessor } from '../../utils/DirectoryProcessor'; - -describe('DirectoryProcessor', () => { - let directoryProcessor: DirectoryProcessor; - let fileSystemManager: FileSystemManager; - let tempDir: string; - - beforeEach(() => { - fileSystemManager = new FileSystemManager(); - directoryProcessor = new DirectoryProcessor(fileSystemManager); - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codebot-dirproc-test-')); - }); - - afterEach(() => { - // Clean up temp directory - if (fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true, force: true }); - } - }); - - describe('scanTemplateStructure', () => { - beforeEach(async () => { - // Create complex directory structure - const structure = { - 'root-file1.hbs': 'root content 1', - 'root-file2.js': 'root content 2', - 'level1/file1.hbs': 'level1 content 1', - 'level1/file2.css': 'level1 content 2', - 'level1/level2/file3.hbs': 'level2 content 1', - 'level1/level2/level3/file4.html': 'level3 content 1', - 'another-level1/file5.hbs': 'another level1 content', - }; - - for (const [filePath, content] of Object.entries(structure)) { - const fullPath = path.join(tempDir, filePath); - await fileSystemManager.createFile(fullPath, content); - } - }); - - it('should scan complete directory structure', async () => { - const structure = await directoryProcessor.scanTemplateStructure(tempDir); - - expect(structure.rootPath).toBe(tempDir); - expect(structure.files).toHaveLength(2); // root-file1.hbs, root-file2.js - expect(structure.directories).toHaveLength(2); // level1, another-level1 - - // Check level1 directory - const level1Dir = structure.directories.find(d => d.name === 'level1'); - expect(level1Dir).toBeDefined(); - expect(level1Dir?.files).toHaveLength(2); // file1.hbs, file2.css - expect(level1Dir?.subdirectories).toHaveLength(1); // level2 - - // Check level2 directory - const level2Dir = level1Dir?.subdirectories.find( - d => d.name === 'level2', - ); - expect(level2Dir).toBeDefined(); - expect(level2Dir?.files).toHaveLength(1); // file3.hbs - expect(level2Dir?.subdirectories).toHaveLength(1); // level3 - - // Check level3 directory - const level3Dir = level2Dir?.subdirectories.find( - d => d.name === 'level3', - ); - expect(level3Dir).toBeDefined(); - expect(level3Dir?.files).toHaveLength(1); // file4.html - expect(level3Dir?.subdirectories).toHaveLength(0); - }); - - it('should set correct relative paths', async () => { - const structure = await directoryProcessor.scanTemplateStructure(tempDir); - - // Check root files - const rootFile = structure.files.find(f => f.name === 'root-file1.hbs'); - expect(rootFile?.relativePath).toBe('root-file1.hbs'); - - // Check nested directory - const level1Dir = structure.directories.find(d => d.name === 'level1'); - expect(level1Dir?.relativePath).toBe('level1'); - - // Check nested file - const nestedFile = level1Dir?.files.find(f => f.name === 'file1.hbs'); - expect(nestedFile?.relativePath).toBe('file1.hbs'); - }); - - it('should handle empty directories', async () => { - const emptyDir = path.join(tempDir, 'empty'); - await fileSystemManager.createFolderRecursive(emptyDir); - - const structure = await directoryProcessor.scanTemplateStructure(tempDir); - - const emptyDirEntry = structure.directories.find(d => d.name === 'empty'); - expect(emptyDirEntry).toBeDefined(); - expect(emptyDirEntry?.files).toHaveLength(0); - expect(emptyDirEntry?.subdirectories).toHaveLength(0); - }); - - it('should throw error for non-existent directory', async () => { - const nonExistentDir = path.join(tempDir, 'nonexistent'); - - await expect( - directoryProcessor.scanTemplateStructure(nonExistentDir), - ).rejects.toMatchObject({ - type: ErrorType.TEMPLATE_PROCESSING_ERROR, - message: expect.stringContaining('Template directory does not exist'), - }); - }); - }); - - describe('validateDepth', () => { - it('should pass validation for normal depth', async () => { - // Create structure with depth 3 - const structure = { - 'level1/level2/level3/file.hbs': 'content', - }; - - for (const [filePath, content] of Object.entries(structure)) { - const fullPath = path.join(tempDir, filePath); - await fileSystemManager.createFile(fullPath, content); - } - - const templateStructure = - await directoryProcessor.scanTemplateStructure(tempDir); - - expect(() => - directoryProcessor.validateDepth(templateStructure), - ).not.toThrow(); - }); - - it('should throw error when maximum depth is exceeded', async () => { - // Create structure with depth > 15 - let deepPath = tempDir; - for (let i = 0; i < 17; i++) { - deepPath = path.join(deepPath, `level${i}`); - } - await fileSystemManager.createFile( - path.join(deepPath, 'file.hbs'), - 'content', - ); - - // The error should be thrown during scanning, not validation - await expect( - directoryProcessor.scanTemplateStructure(tempDir), - ).rejects.toMatchObject({ - type: ErrorType.FILE_SYSTEM_ERROR, - message: expect.stringContaining('Maximum directory depth exceeded'), - }); - }); - }); - - describe('file extension handling', () => { - beforeEach(async () => { - const structure = { - 'template.hbs': 'handlebars template', - 'style.css': 'css content', - 'script.js': 'js content', - 'config.json': 'json content', - 'readme.md': 'markdown content', - }; - - for (const [filePath, content] of Object.entries(structure)) { - const fullPath = path.join(tempDir, filePath); - await fileSystemManager.createFile(fullPath, content); - } - }); - - it('should correctly identify file extensions', async () => { - const structure = await directoryProcessor.scanTemplateStructure(tempDir); - - const hbsFile = structure.files.find(f => f.name === 'template.hbs'); - expect(hbsFile?.extension).toBe('.hbs'); - - const cssFile = structure.files.find(f => f.name === 'style.css'); - expect(cssFile?.extension).toBe('.css'); - - const jsFile = structure.files.find(f => f.name === 'script.js'); - expect(jsFile?.extension).toBe('.js'); - - const jsonFile = structure.files.find(f => f.name === 'config.json'); - expect(jsonFile?.extension).toBe('.json'); - - const mdFile = structure.files.find(f => f.name === 'readme.md'); - expect(mdFile?.extension).toBe('.md'); - }); - }); - - describe('complex nested structures', () => { - beforeEach(async () => { - // Create a realistic component template structure - const structure = { - 'index.hbs': 'export { default } from "./{{name}}";', - '{{name}}.hbs': 'import React from "react";', - '{{name}}.test.hbs': 'import { render } from "@testing-library/react";', - 'styles/index.css.hbs': '@import "./{{name}}.css";', - 'styles/{{name}}.css.hbs': '.{{name}} { display: block; }', - 'styles/themes/light.css.hbs': '.{{name}}.light { background: white; }', - 'styles/themes/dark.css.hbs': '.{{name}}.dark { background: black; }', - 'utils/helpers.js.hbs': 'export const {{name}}Helper = () => {};', - 'utils/constants.js.hbs': 'export const {{name}}_CONSTANTS = {};', - 'assets/icons/icon.svg.hbs': '{{name}}', - 'docs/README.md.hbs': '# {{name}} Component', - }; - - for (const [filePath, content] of Object.entries(structure)) { - const fullPath = path.join(tempDir, filePath); - await fileSystemManager.createFile(fullPath, content); - } - }); - - it('should handle complex nested component structure', async () => { - const structure = await directoryProcessor.scanTemplateStructure(tempDir); - - expect(structure.files).toHaveLength(3); // index.hbs, {{name}}.hbs, {{name}}.test.hbs - expect(structure.directories).toHaveLength(4); // styles, utils, assets, docs - - // Check styles directory - const stylesDir = structure.directories.find(d => d.name === 'styles'); - expect(stylesDir?.files).toHaveLength(2); // index.css.hbs, {{name}}.css.hbs - expect(stylesDir?.subdirectories).toHaveLength(1); // themes - - // Check themes subdirectory - const themesDir = stylesDir?.subdirectories.find( - d => d.name === 'themes', - ); - expect(themesDir?.files).toHaveLength(2); // light.css.hbs, dark.css.hbs - - // Check utils directory - const utilsDir = structure.directories.find(d => d.name === 'utils'); - expect(utilsDir?.files).toHaveLength(2); // helpers.js.hbs, constants.js.hbs - - // Check assets directory with nested icons - const assetsDir = structure.directories.find(d => d.name === 'assets'); - expect(assetsDir?.subdirectories).toHaveLength(1); // icons - - const iconsDir = assetsDir?.subdirectories.find(d => d.name === 'icons'); - expect(iconsDir?.files).toHaveLength(1); // icon.svg.hbs - }); - }); -}); diff --git a/src/types/AppError.ts b/src/types/AppError.ts new file mode 100644 index 0000000..51ee608 --- /dev/null +++ b/src/types/AppError.ts @@ -0,0 +1,29 @@ +export type ErrorCode = + | 'FILE_READ_ERROR' + | 'FILE_WRITE_ERROR' + | 'FOLDER_CREATE_ERROR' + | 'FOLDER_LIST_ERROR' + | 'TEMPLATE_COMPILE_ERROR' + | 'TEMPLATE_DUPLICATE_PATH' + | 'TEMPLATE_MAX_DEPTH_EXCEEDED' + | 'WORKSPACE_NOT_FOUND' + | 'WORKSPACE_FILE_READ_ERROR' + | 'WORKSPACE_FILE_WRITE_ERROR' + | 'WORKSPACE_FOLDER_CREATE_ERROR' + | 'WORKSPACE_DIR_LIST_ERROR'; + +export type AppError = { + readonly code: ErrorCode; + readonly message: string; + readonly cause?: unknown; +}; + +export const makeError = ( + code: ErrorCode, + message: string, + cause?: unknown, +): AppError => ({ + code, + message, + cause, +}); diff --git a/src/types/Config.ts b/src/types/Config.ts new file mode 100644 index 0000000..b591fd3 --- /dev/null +++ b/src/types/Config.ts @@ -0,0 +1,7 @@ +export type CodebotConfig = { + readonly templatesFolderPath: string; +}; + +export const DEFAULT_TEMPLATES_FOLDER = 'templates'; +export const CONFIG_SECTION = 'codebot'; +export const CONFIG_KEY_TEMPLATES_FOLDER = 'templatesFolderPath'; diff --git a/src/types/Option.ts b/src/types/Option.ts new file mode 100644 index 0000000..4911001 --- /dev/null +++ b/src/types/Option.ts @@ -0,0 +1,18 @@ +export type Option = T | undefined; + +export const some = (value: T): Option => value; + +export const none = (): Option => undefined; + +export const isSome = (value: Option): value is T => value !== undefined; + +export const isNone = (value: Option): value is undefined => + value === undefined; + +export const mapOption = ( + value: Option, + fn: (v: T) => U, +): Option => (isSome(value) ? fn(value) : undefined); + +export const getOrElse = (value: Option, fallback: T): T => + isSome(value) ? value : fallback; diff --git a/src/types/Result.ts b/src/types/Result.ts new file mode 100644 index 0000000..065e360 --- /dev/null +++ b/src/types/Result.ts @@ -0,0 +1,30 @@ +export type Result = + | { readonly success: true; readonly value: T } + | { readonly success: false; readonly error: E }; + +export const ok = (value: T): Result => ({ success: true, value }); + +export const err = (error: E): Result => ({ + success: false, + error, +}); + +export const isOk = ( + result: Result, +): result is { success: true; value: T } => result.success; + +export const isErr = ( + result: Result, +): result is { success: false; error: E } => !result.success; + +export const mapResult = ( + result: Result, + fn: (value: T) => U, +): Result => + isOk(result) ? ok(fn(result.value)) : (result as unknown as Result); + +export const flatMapResult = ( + result: Result, + fn: (value: T) => Result, +): Result => + isOk(result) ? fn(result.value) : (result as unknown as Result); diff --git a/src/types/index.ts b/src/types/index.ts index ea73ccf..575fab6 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,135 +1,7 @@ -export interface ConfigurationFile { - templateFolderPath?: string; - multiProject?: { - enabled: boolean; - projectDetection: 'auto' | 'manual'; - }; -} +export type { AppError, ErrorCode } from './AppError'; +export { makeError } from './AppError'; -export interface ProjectContext { - workspaceRoot: string; - projectRoot: string; - templatePath: string; - configPath: string; - isMultiProject: boolean; - projectName?: string; -} - -export interface TemplateType { - name: string; - path: string; - files: string[]; - hasSubdirectories: boolean; - structure: TemplateStructure; -} - -export interface Template { - name: string; - type: string; - files: TemplateFile[]; - metadata: TemplateMetadata; -} - -export interface TemplateFile { - originalName: string; - targetName: string; - content: string; - variables: string[]; -} - -export interface TemplateMetadata { - description?: string; - author?: string; - version?: string; - dependencies?: string[]; -} - -export interface GenerationContext { - componentName: string; - targetPath: string; - templateType: string; - variables: Record; - options: GenerationOptions; -} - -export interface GenerationOptions { - overwriteExisting: boolean; - createMissingDirectories: boolean; - formatCode: boolean; -} - -export interface FileSystemEntry { - name: string; - path: string; - relativePath: string; - isDirectory: boolean; - children?: FileSystemEntry[]; -} - -export interface TemplateStructure { - rootPath: string; - files: TemplateFileInfo[]; - directories: TemplateDirectoryInfo[]; -} - -export interface TemplateFileInfo { - name: string; - path: string; - relativePath: string; - extension: string; -} - -export interface TemplateDirectoryInfo { - name: string; - path: string; - relativePath: string; - files: TemplateFileInfo[]; - subdirectories: TemplateDirectoryInfo[]; -} - -export interface ProcessedTemplate { - componentName: string; - files: ProcessedFile[]; - directories: ProcessedDirectory[]; -} - -export interface ProcessedFile { - originalPath: string; - targetPath: string; - content: string; -} - -export interface ProcessedDirectory { - originalPath: string; - targetPath: string; - files: ProcessedFile[]; - subdirectories: ProcessedDirectory[]; -} - -export enum ErrorType { - WORKSPACE_NOT_FOUND = 'WORKSPACE_NOT_FOUND', - INVALID_COMPONENT_NAME = 'INVALID_COMPONENT_NAME', - TEMPLATE_FOLDER_EMPTY = 'TEMPLATE_FOLDER_EMPTY', - TEMPLATE_NOT_FOUND = 'TEMPLATE_NOT_FOUND', - FILE_SYSTEM_ERROR = 'FILE_SYSTEM_ERROR', - TEMPLATE_PROCESSING_ERROR = 'TEMPLATE_PROCESSING_ERROR', - PROJECT_DETECTION_ERROR = 'PROJECT_DETECTION_ERROR', - CONFIGURATION_ERROR = 'CONFIGURATION_ERROR', -} - -export interface CodebotError { - type: ErrorType; - message: string; - details?: unknown; - recoverable: boolean; -} - -export interface CommandArgs { - fsPath?: string; -} - -export * from '../constants'; -export * from '../errors'; -// Re-export interfaces and utilities for convenience -export * from '../interfaces'; -export * from '../utils'; +export type { Option } from './Option'; +export { getOrElse, isNone, isSome, mapOption, none, some } from './Option'; +export type { Result } from './Result'; +export { err, flatMapResult, isErr, isOk, mapResult, ok } from './Result'; diff --git a/src/utils/DirectoryProcessor.ts b/src/utils/DirectoryProcessor.ts deleted file mode 100644 index 568f099..0000000 --- a/src/utils/DirectoryProcessor.ts +++ /dev/null @@ -1,135 +0,0 @@ -import * as path from 'node:path'; -import { FileSystemManager } from '../managers/FileSystemManager'; -import { - type CodebotError, - ErrorType, - type FileSystemEntry, - type TemplateDirectoryInfo, - type TemplateFileInfo, - type TemplateStructure, -} from '../types'; - -export class DirectoryProcessor { - private static readonly MAX_DEPTH = 15; // Security limit for directory depth - private fileSystemManager: FileSystemManager; - - constructor(fileSystemManager?: FileSystemManager) { - this.fileSystemManager = fileSystemManager || new FileSystemManager(); - } - - async scanTemplateStructure( - templatePath: string, - ): Promise { - try { - if (!(await this.fileSystemManager.folderExists(templatePath))) { - throw this.createError( - `Template directory does not exist: ${templatePath}`, - ); - } - - const entries = - await this.fileSystemManager.listFilesRecursive(templatePath); - return this.buildTemplateStructure(templatePath, entries); - } catch (error) { - if (error && typeof error === 'object' && 'type' in error) { - throw error; // Re-throw CodebotError instances - } - throw this.createError( - `Failed to scan template structure: ${templatePath}`, - error, - ); - } - } - - private buildTemplateStructure( - rootPath: string, - entries: FileSystemEntry[], - ): TemplateStructure { - const files: TemplateFileInfo[] = []; - const directories: TemplateDirectoryInfo[] = []; - - for (const entry of entries) { - if (entry.isDirectory) { - const directoryInfo = this.buildDirectoryInfo(rootPath, entry); - directories.push(directoryInfo); - } else { - const fileInfo = this.buildFileInfo(rootPath, entry); - files.push(fileInfo); - } - } - - return { - rootPath, - files, - directories, - }; - } - - private buildFileInfo( - _rootPath: string, - entry: FileSystemEntry, - ): TemplateFileInfo { - return { - name: entry.name, - path: entry.path, - relativePath: entry.relativePath, - extension: path.extname(entry.name), - }; - } - - private buildDirectoryInfo( - rootPath: string, - entry: FileSystemEntry, - ): TemplateDirectoryInfo { - const files: TemplateFileInfo[] = []; - const subdirectories: TemplateDirectoryInfo[] = []; - - if (entry.children) { - for (const child of entry.children) { - if (child.isDirectory) { - const subDirInfo = this.buildDirectoryInfo(rootPath, child); - subdirectories.push(subDirInfo); - } else { - const fileInfo = this.buildFileInfo(rootPath, child); - files.push(fileInfo); - } - } - } - - return { - name: entry.name, - path: entry.path, - relativePath: entry.relativePath, - files, - subdirectories, - }; - } - - validateDepth(structure: TemplateStructure): void { - this.validateDirectoryDepth(structure.directories, 0); - } - - private validateDirectoryDepth( - directories: TemplateDirectoryInfo[], - currentDepth: number, - ): void { - if (currentDepth > DirectoryProcessor.MAX_DEPTH) { - throw this.createError( - `Maximum directory depth exceeded: ${DirectoryProcessor.MAX_DEPTH}`, - ); - } - - for (const directory of directories) { - this.validateDirectoryDepth(directory.subdirectories, currentDepth + 1); - } - } - - private createError(message: string, originalError?: unknown): CodebotError { - return { - type: ErrorType.TEMPLATE_PROCESSING_ERROR, - message, - details: originalError, - recoverable: false, - }; - } -} diff --git a/src/utils/PathResolver.ts b/src/utils/PathResolver.ts deleted file mode 100644 index ac02d11..0000000 --- a/src/utils/PathResolver.ts +++ /dev/null @@ -1,326 +0,0 @@ -import * as path from 'node:path'; -import { CodebotError } from '../errors'; -import { ErrorType, type IPathResolver, type ProjectContext } from '../types'; - -export class PathResolver implements IPathResolver { - private static readonly FORBIDDEN_PATTERNS = [ - /\.\./, // Parent directory traversal - /^\/+/, // Absolute paths starting with / - /^[a-zA-Z]:\\/, // Windows absolute paths - /\0/, // Null bytes - /[<>:"|?*]/, // Invalid filename characters - ]; - - private static readonly MAX_PATH_LENGTH = 260; // Windows MAX_PATH limitation - private static readonly MAX_COMPONENT_NAME_LENGTH = 200; // Reasonable component name limit - private static readonly RESERVED_NAMES = [ - 'CON', - 'PRN', - 'AUX', - 'NUL', - 'COM1', - 'COM2', - 'COM3', - 'COM4', - 'COM5', - 'COM6', - 'COM7', - 'COM8', - 'COM9', - 'LPT1', - 'LPT2', - 'LPT3', - 'LPT4', - 'LPT5', - 'LPT6', - 'LPT7', - 'LPT8', - 'LPT9', - ]; - - /** - * Resolves the template path based on project context - * Follows hierarchy: project-specific templates → workspace templates → fallback - */ - resolveTemplatePath( - projectContext: ProjectContext, - templateName: string, - ): string { - // Check for dangerous security patterns first - if (!this.validatePathSecurity(templateName)) { - throw new CodebotError( - ErrorType.FILE_SYSTEM_ERROR, - `Invalid template name: ${templateName}`, - { templateName }, - false, - ); - } - - const sanitizedTemplateName = this.sanitizePath(templateName); - - // For multi-project workspaces, prefer project-specific templates - if (projectContext.isMultiProject) { - const projectTemplatePath = path.join( - projectContext.projectRoot, - 'templates', - sanitizedTemplateName, - ); - return this.normalizePath(projectTemplatePath); - } - - // For single project or fallback, use workspace-level templates - const workspaceTemplatePath = path.join( - projectContext.workspaceRoot, - 'templates', - sanitizedTemplateName, - ); - return this.normalizePath(workspaceTemplatePath); - } - - /** - * Resolves the target path where component will be created - * Ensures path is within project boundaries - */ - resolveTargetPath( - projectContext: ProjectContext, - componentName: string, - selectedFolderPath?: string, - ): string { - // Check for dangerous security patterns first - if (!this.validatePathSecurity(componentName)) { - throw new CodebotError( - ErrorType.INVALID_COMPONENT_NAME, - `Invalid component name: ${componentName}`, - { componentName }, - true, - ); - } - - const sanitizedComponentName = this.sanitizePath(componentName); - - // Determine the base path for component creation - const basePath = this.resolveBasePath(projectContext, selectedFolderPath); - const targetPath = path.join(basePath, sanitizedComponentName); - - // Ensure the resolved path is within project boundaries - if (!this.isWithinProjectBoundaries(targetPath, projectContext)) { - throw new CodebotError( - ErrorType.FILE_SYSTEM_ERROR, - `Target path is outside project boundaries: ${targetPath}`, - { targetPath, projectRoot: projectContext.projectRoot }, - false, - ); - } - - return this.normalizePath(targetPath); - } - - /** - * Validates a path for security and correctness (strict validation) - */ - validatePath(inputPath: string): boolean { - if (!inputPath || typeof inputPath !== 'string') { - return false; - } - - // Check path length - if (inputPath.length > PathResolver.MAX_PATH_LENGTH) { - return false; - } - - // Check for forbidden patterns - for (const pattern of PathResolver.FORBIDDEN_PATTERNS) { - if (pattern.test(inputPath)) { - return false; - } - } - - // Check for reserved names (Windows) - const pathParts = inputPath.split(/[/\\]/); - for (const part of pathParts) { - const upperPart = part.toUpperCase(); - if (PathResolver.RESERVED_NAMES.includes(upperPart)) { - return false; - } - } - - // Check for empty path segments - if (pathParts.some(part => part === '')) { - return false; - } - - return true; - } - - /** - * Validates a path strictly for dangerous security patterns only - */ - private validatePathSecurity(inputPath: string): boolean { - if (!inputPath || typeof inputPath !== 'string') { - return false; - } - - // Check component name length (reasonable limit) - if (inputPath.length > PathResolver.MAX_COMPONENT_NAME_LENGTH) { - return false; - } - - // Check for dangerous patterns only - const dangerousPatterns = [ - /\.\./, // Parent directory traversal - /^\/+/, // Absolute paths starting with / - /^[a-zA-Z]:\\/, // Windows absolute paths - /\0/, // Null bytes - ]; - - for (const pattern of dangerousPatterns) { - if (pattern.test(inputPath)) { - return false; - } - } - - return true; - } - - /** - * Sanitizes a path by removing dangerous characters and patterns - */ - sanitizePath(inputPath: string): string { - if (!inputPath || typeof inputPath !== 'string') { - return ''; - } - - let sanitized = inputPath - // Remove null bytes - .replace(/\0/g, '') - // Remove or replace invalid characters - .replace(/[<>:"|?*]/g, '_') - // Remove leading/trailing whitespace and dots - .trim() - .replace(/^\.+|\.+$/g, '') - // Normalize path separators - .replace(/[/\\]+/g, path.sep) - // Remove parent directory references - .replace(/\.\.[/\\]/g, '') - .replace(/\.\.$/, ''); - - // Ensure it doesn't start with a path separator - sanitized = sanitized.replace(/^[/\\]+/, ''); - - // Handle reserved names by appending underscore - const pathParts = sanitized.split(path.sep); - const sanitizedParts = pathParts.map(part => { - const upperPart = part.toUpperCase(); - if (PathResolver.RESERVED_NAMES.includes(upperPart)) { - return `${part}_`; - } - return part; - }); - - return sanitizedParts.join(path.sep); - } - - /** - * Resolves the configuration file path for a project - */ - resolveConfigPath(projectContext: ProjectContext): string { - // For multi-project, prefer project-specific config - if (projectContext.isMultiProject) { - const projectConfigPath = path.join( - projectContext.projectRoot, - 'codebot.config.json', - ); - return this.normalizePath(projectConfigPath); - } - - // For single project, use workspace-level config - const workspaceConfigPath = path.join( - projectContext.workspaceRoot, - 'codebot.config.json', - ); - return this.normalizePath(workspaceConfigPath); - } - - /** - * Resolves relative paths to absolute paths within project context - */ - resolveRelativePath( - projectContext: ProjectContext, - relativePath: string, - ): string { - // Check for dangerous security patterns first - if (!this.validatePathSecurity(relativePath)) { - throw new CodebotError( - ErrorType.FILE_SYSTEM_ERROR, - `Invalid relative path: ${relativePath}`, - { relativePath }, - false, - ); - } - - const sanitizedPath = this.sanitizePath(relativePath); - - const basePath = this.resolveBasePath(projectContext); - const absolutePath = path.resolve(basePath, sanitizedPath); - - // Ensure the resolved path is within project boundaries - if (!this.isWithinProjectBoundaries(absolutePath, projectContext)) { - throw new CodebotError( - ErrorType.FILE_SYSTEM_ERROR, - `Resolved path is outside project boundaries: ${absolutePath}`, - { relativePath, absolutePath, projectRoot: projectContext.projectRoot }, - false, - ); - } - - return this.normalizePath(absolutePath); - } - - /** - * Determines the base path for operations based on project context - */ - private resolveBasePath( - projectContext: ProjectContext, - selectedFolderPath?: string, - ): string { - // If a specific folder was selected, use that as the base path - if (selectedFolderPath) { - // Ensure the selected folder is within project boundaries - if (this.isWithinProjectBoundaries(selectedFolderPath, projectContext)) { - return selectedFolderPath; - } - } - - // Fallback to project root - return projectContext.projectRoot; - } - - /** - * Checks if a path is within the allowed project boundaries - */ - private isWithinProjectBoundaries( - targetPath: string, - projectContext: ProjectContext, - ): boolean { - const normalizedTarget = this.normalizePath(targetPath); - const normalizedProjectRoot = this.normalizePath( - projectContext.projectRoot, - ); - const normalizedWorkspaceRoot = this.normalizePath( - projectContext.workspaceRoot, - ); - - // Path must be within either project root or workspace root - return ( - normalizedTarget.startsWith(normalizedProjectRoot) || - normalizedTarget.startsWith(normalizedWorkspaceRoot) - ); - } - - /** - * Normalizes path separators and resolves relative components - */ - private normalizePath(inputPath: string): string { - return path.resolve(inputPath).replace(/\\/g, '/'); - } -} diff --git a/src/utils/PathResolverExample.ts b/src/utils/PathResolverExample.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/utils/formatToPascalCase.ts b/src/utils/formatToPascalCase.ts new file mode 100644 index 0000000..799d0dc --- /dev/null +++ b/src/utils/formatToPascalCase.ts @@ -0,0 +1,13 @@ +export const formatToPascalCase = (input: string): string => { + const words = input + .trim() + .replace(/([a-z])([A-Z])/g, '$1 $2') + .split(/[^a-zA-Z0-9]+/) + .filter(Boolean); + + if (words.length === 0) return ''; + + return words + .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join(''); +}; diff --git a/src/utils/index.ts b/src/utils/index.ts deleted file mode 100644 index 69f1063..0000000 --- a/src/utils/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './PathResolver'; -export * from './validation'; diff --git a/src/utils/validation.ts b/src/utils/validation.ts index e587067..aa760ec 100644 --- a/src/utils/validation.ts +++ b/src/utils/validation.ts @@ -1,32 +1,11 @@ -import { COMPONENT_NAME_REGEX } from '../constants'; +import { formatToPascalCase } from './formatToPascalCase'; -export function isValidComponentName(name: string): boolean { - if (!name || typeof name !== 'string') { - return false; - } - return COMPONENT_NAME_REGEX.test(name.trim()); -} +export { formatToPascalCase }; -export function sanitizeComponentName(name: string): string { - return name.trim().replace(/[^a-zA-Z0-9_-]/g, ''); -} +const COMPONENT_NAME_RE = /^[A-Z][A-Za-z0-9]*$/; -export function isValidPath(filePath: string): boolean { - if (!filePath || typeof filePath !== 'string') { - return false; - } - const normalizedPath = filePath.replace(/\\/g, '/'); - return !normalizedPath.includes('../') && !normalizedPath.includes('./'); -} +export const isValidComponentName = (name: string): boolean => + COMPONENT_NAME_RE.test(name); -export function sanitizePath(filePath: string): string { - return filePath.replace(/[<>:"|?*]/g, '').replace(/\.\./g, ''); -} - -export function isTemplateFile(filename: string): boolean { - return filename.endsWith('.hbs'); -} - -export function removeTemplateExtension(filename: string): string { - return filename.replace(/\.hbs$/, ''); -} +export const COMPONENT_NAME_ERROR = + 'Must start with a letter. Try "Button", "my-card", or "myComponent".'; From e90221d897699bfe987b50d538985b6d9a03a92a Mon Sep 17 00:00:00 2001 From: Douglas Gomes Date: Fri, 17 Apr 2026 23:10:51 -0300 Subject: [PATCH 08/17] chore: update GitHub workflows to use npm install instead of npm ci - Changed the dependency installation command from 'npm ci' to 'npm install' in both pull_request.yml and push_main.yml. - Updated the test command to 'npm run test' for consistency across workflows. --- .github/workflows/pull_request.yml | 8 ++++---- .github/workflows/push_main.yml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index f23f371..544efea 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -22,7 +22,7 @@ jobs: cache: "npm" - name: Install Dependencies - run: npm ci + run: npm install - name: Checking code style run: npm run lint:check @@ -40,10 +40,10 @@ jobs: cache: "npm" - name: Install Dependencies - run: npm ci + run: npm install - name: Running tests - run: npm test + run: npm run test build: name: Build @@ -58,7 +58,7 @@ jobs: cache: "npm" - name: Install Dependencies - run: npm ci + run: npm install - name: Building run: npm run build diff --git a/.github/workflows/push_main.yml b/.github/workflows/push_main.yml index 17e72b0..1f224b2 100644 --- a/.github/workflows/push_main.yml +++ b/.github/workflows/push_main.yml @@ -21,13 +21,13 @@ jobs: cache: "npm" - name: Install Dependencies - run: npm ci + run: npm install - name: Checking code style run: npm run lint:check - name: Running tests - run: npm test + run: npm run test - name: Create Release env: From b3684b7dd8b275c45446b1def7b018b84bdf8e1b Mon Sep 17 00:00:00 2001 From: Douglas Gomes Date: Fri, 17 Apr 2026 23:10:57 -0300 Subject: [PATCH 09/17] fix: update jest.config.js for coverage and module path adjustments - Modified collectCoverageFrom to exclude test and mock directories for better coverage reporting. - Updated moduleNameMapper path for vscode mock to reflect new directory structure. - Adjusted setupFilesAfterEnv path for consistency with project organization. --- jest.config.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/jest.config.js b/jest.config.js index 7f0c935..69992eb 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,13 +6,17 @@ module.exports = { transform: { '^.+\\.ts$': 'ts-jest', }, - collectCoverageFrom: ['src/**/*.ts', '!src/**/*.d.ts', '!src/test/**/*'], + collectCoverageFrom: [ + 'src/**/*.ts', + '!src/**/*.d.ts', + '!src/__tests__/**/*', + '!src/__mocks__/**/*', + ], coverageDirectory: 'coverage', coverageReporters: ['text', 'lcov', 'html'], moduleNameMapper: { - '^vscode$': '/src/test/__mocks__/vscode.js', + '^vscode$': '/src/__mocks__/vscode.js', }, - setupFilesAfterEnv: ['/src/test/setup.ts'], maxWorkers: 1, forceExit: true, detectOpenHandles: true, From 1e97ed9d395c6beaf253a160bdf2847827c4edd3 Mon Sep 17 00:00:00 2001 From: Douglas Gomes Date: Fri, 17 Apr 2026 23:23:17 -0300 Subject: [PATCH 10/17] feat: update ComponentTailwind styles and VSCode settings - Replaced createStyles with tv from tailwind-variants in ComponentTailwind.style.ts.hbs for improved styling. - Added templatesFolderPath configuration in VSCode settings to specify the examples directory. --- .vscode/settings.json | 4 +++- examples/ComponentTailwind/ComponentTailwind.style.ts.hbs | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 3852689..90a9182 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -30,5 +30,7 @@ "typescript.tsc.autoDetect": "off", - "cSpell.words": ["codebot", "douglasgomes", "styfle", "vsix"] + "cSpell.words": ["codebot", "douglasgomes", "styfle", "vsix"], + + "codebot.templatesFolderPath": "examples" } diff --git a/examples/ComponentTailwind/ComponentTailwind.style.ts.hbs b/examples/ComponentTailwind/ComponentTailwind.style.ts.hbs index 411564c..29c632d 100644 --- a/examples/ComponentTailwind/ComponentTailwind.style.ts.hbs +++ b/examples/ComponentTailwind/ComponentTailwind.style.ts.hbs @@ -1,3 +1,3 @@ -import { createStyles } from 'ds'; +import { tv } from 'tailwind-variants'; -export const {{name}}Style = createStyles({}) +export const {{name}}Style = tv({}) From 4f608862419a780ff948b1b3878467b67dd3277a Mon Sep 17 00:00:00 2001 From: Douglas Gomes Date: Sat, 18 Apr 2026 13:11:49 -0300 Subject: [PATCH 11/17] chore: update VSCode configuration and remove obsolete settings - Removed unused test-related configurations from launch.json and tasks.json for cleaner setup. - Updated .vscodeignore to include new files and directories, enhancing project structure. - Revised README.md to clarify the configuration process for Codebot settings. --- .vscode/launch.json | 11 ----------- .vscode/tasks.json | 10 ---------- .vscodeignore | 10 +++++----- README.md | 6 +++--- 4 files changed, 8 insertions(+), 29 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 367a9ae..ccdb134 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,17 +12,6 @@ "args": ["--extensionDevelopmentPath=${workspaceFolder}"], "outFiles": ["${workspaceFolder}/dist/**/*.js"], "preLaunchTask": "${defaultBuildTask}" - }, - { - "name": "Extension Tests", - "type": "extensionHost", - "request": "launch", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}", - "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" - ], - "outFiles": ["${workspaceFolder}/out/test/**/*.js"], - "preLaunchTask": "npm: test-watch" } ] } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index d030878..0a5c6ca 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -15,16 +15,6 @@ "kind": "build", "isDefault": true } - }, - { - "type": "npm", - "script": "test-watch", - "problemMatcher": "$tsc-watch", - "isBackground": true, - "presentation": { - "reveal": "never" - }, - "group": "build" } ] } diff --git a/.vscodeignore b/.vscodeignore index 16b27e1..fc0c885 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -1,24 +1,24 @@ +.claude .editorconfig -.eslintrc.json .git .github .gitignore -.nvmrc -.prettierrc.json .releaserc.json +.tool-versions .vscode .vscode-test/** .vscode/** .vscodeignore -**/.eslintrc.json **/*.map **/*.ts **/tsconfig.json +biome.json docs examples +jest.config.js node_modules/** out/** src/** +test-workspace tsconfig.json -vsc-extension-quickstart.md webpack.config.js diff --git a/README.md b/README.md index 02f0ae3..c1f0dc9 100644 --- a/README.md +++ b/README.md @@ -40,11 +40,11 @@ As in the gif example, you can press the mouse right-click button and select bet ### Changing the configurations -1. Creates this file "codebot.config.json" +Open VS Code settings (`Cmd+,` / `Ctrl+,`) and search for **Codebot**, or add the following to your `.vscode/settings.json`: -``` +```json { - "templateFolderPath": "src/templates" + "codebot.templatesFolderPath": "src/templates" } ``` From f7147780a4cf18e86d3fbee777c9e62cab4a1cbf Mon Sep 17 00:00:00 2001 From: Douglas Gomes Date: Sat, 18 Apr 2026 13:16:58 -0300 Subject: [PATCH 12/17] feat: enhance createComponent functionality with template selection - Added logic to prompt for template selection when multiple templates are available. - Updated tests to mock user input for component name, ensuring consistent behavior during testing. --- .../commands/createComponent.test.ts | 1 + src/commands/createComponent.ts | 20 +++++++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/__tests__/commands/createComponent.test.ts b/src/__tests__/commands/createComponent.test.ts index a0f3bfd..8b40eb9 100644 --- a/src/__tests__/commands/createComponent.test.ts +++ b/src/__tests__/commands/createComponent.test.ts @@ -81,6 +81,7 @@ describe('createComponent', () => { ['ComponentSass', 2], ['ComponentTailwind', 2], ] as never); + mockWindow.showInputBox.mockResolvedValue('Button'); mockWindow.showQuickPick.mockResolvedValue(undefined); await createComponent(clickedUri); diff --git a/src/commands/createComponent.ts b/src/commands/createComponent.ts index 6337052..dcfd388 100644 --- a/src/commands/createComponent.ts +++ b/src/commands/createComponent.ts @@ -61,16 +61,6 @@ export const createComponent = async ( return; } - let templateName: string | undefined; - if (templatesResult.value.length === 1) { - templateName = templatesResult.value[0]; - } else { - templateName = await promptSelection(templatesResult.value, { - placeholder: 'Select a template', - }); - } - if (!templateName) return; - const rawName = await promptInput({ prompt: 'Component name', placeholder: 'e.g. Button', @@ -81,6 +71,16 @@ export const createComponent = async ( }); if (!rawName) return; + let templateName: string | undefined; + if (templatesResult.value.length === 1) { + templateName = templatesResult.value[0]; + } else { + templateName = await promptSelection(templatesResult.value, { + placeholder: 'Select a template', + }); + } + if (!templateName) return; + const componentName = formatToPascalCase(rawName.trim()); const templateFolderUri = vscode.Uri.joinPath(templatesUri, templateName); From 212c4e1d1d01807f7d679716ae9ef0bf1c68caf1 Mon Sep 17 00:00:00 2001 From: Douglas Gomes Date: Sat, 18 Apr 2026 13:25:43 -0300 Subject: [PATCH 13/17] chore: add MIT License to the project - Introduced a new LICENSE file containing the MIT License text, outlining permissions and limitations for software use. --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ba52538 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Douglas Gomes + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From bed7f24cc8f07dcdc1d0c4480432fe9aa87795ba Mon Sep 17 00:00:00 2001 From: Douglas Gomes Date: Sat, 18 Apr 2026 14:08:41 -0300 Subject: [PATCH 14/17] feat: introduce template settings for component name formatting - Added support for per-template name formatting options in package.json and VSCode settings. - Updated createComponent and updateComponent commands to utilize the specified name format when generating component names. - Enhanced tests to validate the new name formatting functionality across different templates. - Introduced utility functions for formatting names in various styles (pascal-case, kebab-case, camel-case, snake-case). --- .vscode/settings.json | 5 +- examples/ReactPackage/index.html.hbs | 2 +- examples/ReactPackage/src/App.tsx.hbs | 2 +- examples/ReactPackage/src/main.tsx.hbs | 2 - package.json | 28 +++ .../commands/createComponent.test.ts | 91 +++++++++- .../commands/updateComponent.test.ts | 88 +++++++++- .../helpers/config/readConfig.test.ts | 106 ++++++++++- src/__tests__/utils/formatName.test.ts | 164 ++++++++++++++++++ src/__tests__/utils/validation.test.ts | 21 +++ src/commands/createComponent.ts | 19 +- src/commands/updateComponent.ts | 12 +- src/helpers/config/readConfig.ts | 14 ++ src/types/Config.ts | 19 ++ src/utils/formatName.ts | 32 ++++ src/utils/validation.ts | 3 +- 16 files changed, 572 insertions(+), 36 deletions(-) create mode 100644 src/__tests__/utils/formatName.test.ts create mode 100644 src/utils/formatName.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 90a9182..d1e549e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -32,5 +32,8 @@ "cSpell.words": ["codebot", "douglasgomes", "styfle", "vsix"], - "codebot.templatesFolderPath": "examples" + "codebot.templatesFolderPath": "examples", + "codebot.templateSettings": { + "ReactPackage": { "nameFormat": "kebab-case" } + } } diff --git a/examples/ReactPackage/index.html.hbs b/examples/ReactPackage/index.html.hbs index a4a2376..0437cb9 100644 --- a/examples/ReactPackage/index.html.hbs +++ b/examples/ReactPackage/index.html.hbs @@ -3,7 +3,7 @@ - SKY DS - {{name}} + {{name}}
diff --git a/examples/ReactPackage/src/App.tsx.hbs b/examples/ReactPackage/src/App.tsx.hbs index cd61342..49cd4f5 100644 --- a/examples/ReactPackage/src/App.tsx.hbs +++ b/examples/ReactPackage/src/App.tsx.hbs @@ -1,7 +1,7 @@ function App() { return (
-

SKY DS - {{name}}

+

{name}}

); } diff --git a/examples/ReactPackage/src/main.tsx.hbs b/examples/ReactPackage/src/main.tsx.hbs index 4440155..fa74c07 100644 --- a/examples/ReactPackage/src/main.tsx.hbs +++ b/examples/ReactPackage/src/main.tsx.hbs @@ -1,8 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; -import '@agentguru/foundations'; - import App from './App'; ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( diff --git a/package.json b/package.json index 2e8c037..ab092a8 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,34 @@ "type": "string", "default": "templates", "markdownDescription": "Path to the templates folder, relative to the workspace root." + }, + "codebot.templateSettings": { + "type": "object", + "default": {}, + "markdownDescription": "Per-template settings. Keys must match template folder names.", + "additionalProperties": { + "type": "object", + "properties": { + "nameFormat": { + "type": "string", + "enum": [ + "pascal-case", + "kebab-case", + "camel-case", + "snake-case" + ], + "enumDescriptions": [ + "MyComponent (default)", + "my-component", + "myComponent", + "my_component" + ], + "default": "pascal-case", + "description": "How the component name is formatted when generating files." + } + }, + "additionalProperties": false + } } } }, diff --git a/src/__tests__/commands/createComponent.test.ts b/src/__tests__/commands/createComponent.test.ts index 8b40eb9..b2ee9b1 100644 --- a/src/__tests__/commands/createComponent.test.ts +++ b/src/__tests__/commands/createComponent.test.ts @@ -12,9 +12,16 @@ const encode = (s: string) => new TextEncoder().encode(s); const clickedUri = vscode.Uri.file('/test/workspace/src/components'); -const setupConfig = (templatesFolderPath = 'templates') => { +const setupConfig = ( + templatesFolderPath = 'templates', + templateSettings: Record = {}, +) => { mockGetConfiguration.mockReturnValue({ - get: jest.fn().mockReturnValue(templatesFolderPath), + get: jest.fn().mockImplementation((key: string) => { + if (key === 'templatesFolderPath') return templatesFolderPath; + if (key === 'templateSettings') return templateSettings; + return undefined; + }), update: jest.fn(), has: jest.fn(), inspect: jest.fn(), @@ -382,7 +389,7 @@ describe('createComponent', () => { }); }); - describe('PascalCase formatting', () => { + describe('name formatting (default: pascal-case)', () => { const setupTemplate = () => { mockFs.readDirectory .mockResolvedValueOnce([['ComponentSass', 2]] as never) @@ -426,4 +433,82 @@ describe('createComponent', () => { ); }); }); + + describe('nameFormat per template', () => { + const setupTemplateWithFormat = ( + templateName: string, + nameFormat: string, + ) => { + setupConfig('templates', { [templateName]: { nameFormat } }); + mockFs.readDirectory + .mockResolvedValueOnce([[templateName, 2]] as never) + .mockResolvedValueOnce([['index.hbs', 1]] as never); + mockFs.readFile.mockResolvedValue(encode('// {{name}}') as never); + mockFs.stat.mockRejectedValue(new Error('not found')); + mockFs.createDirectory.mockResolvedValue(undefined); + mockFs.writeFile.mockResolvedValue(undefined); + }; + + it('applies kebab-case format when configured for the selected template', async () => { + setupTemplateWithFormat('ReactPackage', 'kebab-case'); + mockWindow.showInputBox.mockResolvedValue('my-package'); + + await createComponent(clickedUri); + + expect(mockWindow.showInformationMessage).toHaveBeenCalledWith( + expect.stringContaining("'my-package' created"), + ); + }); + + it('applies camel-case format when configured', async () => { + setupTemplateWithFormat('MyTemplate', 'camel-case'); + mockWindow.showInputBox.mockResolvedValue('my-component'); + + await createComponent(clickedUri); + + expect(mockWindow.showInformationMessage).toHaveBeenCalledWith( + expect.stringContaining("'myComponent' created"), + ); + }); + + it('applies snake-case format when configured', async () => { + setupTemplateWithFormat('MyTemplate', 'snake-case'); + mockWindow.showInputBox.mockResolvedValue('MyComponent'); + + await createComponent(clickedUri); + + expect(mockWindow.showInformationMessage).toHaveBeenCalledWith( + expect.stringContaining("'my_component' created"), + ); + }); + + it('falls back to pascal-case when template has no nameFormat setting', async () => { + setupConfig('templates', {}); + mockFs.readDirectory + .mockResolvedValueOnce([['ComponentSass', 2]] as never) + .mockResolvedValueOnce([['ComponentSass.tsx.hbs', 1]] as never); + mockFs.readFile.mockResolvedValue(encode('// {{name}}') as never); + mockFs.stat.mockRejectedValue(new Error('not found')); + mockFs.createDirectory.mockResolvedValue(undefined); + mockFs.writeFile.mockResolvedValue(undefined); + mockWindow.showInputBox.mockResolvedValue('my-button'); + + await createComponent(clickedUri); + + expect(mockWindow.showInformationMessage).toHaveBeenCalledWith( + expect.stringContaining("'MyButton' created"), + ); + }); + + it('uses formatted name in compiled template content', async () => { + setupTemplateWithFormat('ReactPackage', 'kebab-case'); + mockWindow.showInputBox.mockResolvedValue('my-package'); + + await createComponent(clickedUri); + + const [, content] = mockFs.writeFile.mock.calls[0] ?? []; + const text = new TextDecoder().decode(content as Uint8Array); + expect(text).toBe('// my-package'); + }); + }); }); diff --git a/src/__tests__/commands/updateComponent.test.ts b/src/__tests__/commands/updateComponent.test.ts index 43e114a..dddd234 100644 --- a/src/__tests__/commands/updateComponent.test.ts +++ b/src/__tests__/commands/updateComponent.test.ts @@ -13,9 +13,16 @@ const encode = (s: string) => new TextEncoder().encode(s); // Clicking directly on the component folder (e.g. src/components/Button) const clickedUri = vscode.Uri.file('/test/workspace/src/components/Button'); -const setupConfig = (templatesFolderPath = 'templates') => { +const setupConfig = ( + templatesFolderPath = 'templates', + templateSettings: Record = {}, +) => { mockGetConfiguration.mockReturnValue({ - get: jest.fn().mockReturnValue(templatesFolderPath), + get: jest.fn().mockImplementation((key: string) => { + if (key === 'templatesFolderPath') return templatesFolderPath; + if (key === 'templateSettings') return templateSettings; + return undefined; + }), update: jest.fn(), has: jest.fn(), inspect: jest.fn(), @@ -216,7 +223,7 @@ describe('updateComponent', () => { }); }); - describe('PascalCase formatting', () => { + describe('name formatting (default: pascal-case)', () => { it('converts a kebab-case folder name to PascalCase', async () => { const kebabUri = vscode.Uri.file( '/test/workspace/src/components/my-button', @@ -260,6 +267,81 @@ describe('updateComponent', () => { }); }); + describe('nameFormat per template', () => { + const setupTemplateWithFormat = ( + templateName: string, + nameFormat: string, + ) => { + setupConfig('templates', { [templateName]: { nameFormat } }); + mockFs.readDirectory + .mockResolvedValueOnce([[templateName, 2]] as never) + .mockResolvedValueOnce([['index.hbs', 1]] as never); + mockFs.readFile.mockResolvedValue(encode('// {{name}}') as never); + mockFs.stat.mockRejectedValue(new Error('not found')); + mockFs.createDirectory.mockResolvedValue(undefined); + mockFs.writeFile.mockResolvedValue(undefined); + }; + + it('applies kebab-case format: keeps kebab folder name as-is', async () => { + const kebabUri = vscode.Uri.file( + '/test/workspace/src/packages/my-package', + ); + setupTemplateWithFormat('ReactPackage', 'kebab-case'); + + await updateComponent(kebabUri); + + expect(mockWindow.showInformationMessage).toHaveBeenCalledWith( + expect.stringContaining("'my-package' updated"), + ); + }); + + it('applies kebab-case format: converts PascalCase folder name to kebab', async () => { + const pascalUri = vscode.Uri.file( + '/test/workspace/src/packages/MyPackage', + ); + setupTemplateWithFormat('ReactPackage', 'kebab-case'); + + await updateComponent(pascalUri); + + expect(mockWindow.showInformationMessage).toHaveBeenCalledWith( + expect.stringContaining("'my-package' updated"), + ); + }); + + it('applies camel-case format', async () => { + const uri = vscode.Uri.file('/test/workspace/src/components/my-button'); + setupTemplateWithFormat('MyTemplate', 'camel-case'); + + await updateComponent(uri); + + expect(mockWindow.showInformationMessage).toHaveBeenCalledWith( + expect.stringContaining("'myButton' updated"), + ); + }); + + it('applies snake-case format', async () => { + const uri = vscode.Uri.file('/test/workspace/src/components/MyButton'); + setupTemplateWithFormat('MyTemplate', 'snake-case'); + + await updateComponent(uri); + + expect(mockWindow.showInformationMessage).toHaveBeenCalledWith( + expect.stringContaining("'my_button' updated"), + ); + }); + + it('uses formatted name in compiled template content', async () => { + const uri = vscode.Uri.file('/test/workspace/src/packages/my-package'); + setupTemplateWithFormat('ReactPackage', 'kebab-case'); + + await updateComponent(uri); + + const [, content] = mockFs.writeFile.mock.calls[0] ?? []; + const text = new TextDecoder().decode(content as Uint8Array); + expect(text).toBe('// my-package'); + }); + }); + describe('error scenarios', () => { it('shows error when template processing fails', async () => { mockFs.readDirectory diff --git a/src/__tests__/helpers/config/readConfig.test.ts b/src/__tests__/helpers/config/readConfig.test.ts index f718f07..e2be5c5 100644 --- a/src/__tests__/helpers/config/readConfig.test.ts +++ b/src/__tests__/helpers/config/readConfig.test.ts @@ -1,5 +1,5 @@ import * as vscode from 'vscode'; -import { readConfig } from '../../../helpers/config/readConfig'; +import { getNameFormat, readConfig } from '../../../helpers/config/readConfig'; const mockGetConfiguration = vscode.workspace .getConfiguration as jest.MockedFunction< @@ -8,6 +8,17 @@ const mockGetConfiguration = vscode.workspace const mockGet = jest.fn(); +const setupGet = ( + templatesFolderPath: string | undefined, + templateSettings: Record | undefined = {}, +) => { + mockGet.mockImplementation((key: string) => { + if (key === 'templatesFolderPath') return templatesFolderPath; + if (key === 'templateSettings') return templateSettings; + return undefined; + }); +}; + beforeEach(() => { jest.clearAllMocks(); mockGetConfiguration.mockReturnValue({ @@ -21,7 +32,7 @@ beforeEach(() => { describe('readConfig', () => { describe('templatesFolderPath', () => { it('returns the configured value when set', () => { - mockGet.mockReturnValue('custom/templates'); + setupGet('custom/templates'); const config = readConfig(); @@ -29,7 +40,7 @@ describe('readConfig', () => { }); it('returns default "templates" when setting is not defined', () => { - mockGet.mockReturnValue(undefined); + setupGet(undefined); const config = readConfig(); @@ -37,7 +48,7 @@ describe('readConfig', () => { }); it('calls getConfiguration with "codebot" section', () => { - mockGet.mockReturnValue('templates'); + setupGet('templates'); readConfig(); @@ -46,7 +57,7 @@ describe('readConfig', () => { it('passes scope URI to getConfiguration when provided', () => { const uri = vscode.Uri.file('/workspace/my-package'); - mockGet.mockReturnValue('templates'); + setupGet('templates'); readConfig(uri); @@ -54,7 +65,7 @@ describe('readConfig', () => { }); it('reads the correct key from the configuration', () => { - mockGet.mockReturnValue('src/templates'); + setupGet('src/templates'); readConfig(); @@ -62,14 +73,91 @@ describe('readConfig', () => { }); }); + describe('templateSettings', () => { + it('returns the configured templateSettings when set', () => { + setupGet('templates', { ReactPackage: { nameFormat: 'kebab-case' } }); + + const config = readConfig(); + + expect(config.templateSettings).toEqual({ + ReactPackage: { nameFormat: 'kebab-case' }, + }); + }); + + it('returns empty object when templateSettings is not defined', () => { + setupGet('templates', undefined); + + const config = readConfig(); + + expect(config.templateSettings).toEqual({}); + }); + + it('reads the correct key from the configuration', () => { + setupGet('templates'); + + readConfig(); + + expect(mockGet).toHaveBeenCalledWith('templateSettings'); + }); + }); + describe('return shape', () => { - it('returns a frozen-compatible plain object', () => { - mockGet.mockReturnValue('templates'); + it('returns an object with both fields', () => { + setupGet('templates', {}); const config = readConfig(); - expect(config).toEqual({ templatesFolderPath: 'templates' }); + expect(config).toEqual({ + templatesFolderPath: 'templates', + templateSettings: {}, + }); expect(typeof config).toBe('object'); }); }); }); + +describe('getNameFormat', () => { + it('returns the configured nameFormat for a known template', () => { + const config = { + templatesFolderPath: 'templates', + templateSettings: { ReactPackage: { nameFormat: 'kebab-case' as const } }, + }; + + expect(getNameFormat(config, 'ReactPackage')).toBe('kebab-case'); + }); + + it('returns "pascal-case" when template has no nameFormat', () => { + const config = { + templatesFolderPath: 'templates', + templateSettings: { ReactPackage: {} }, + }; + + expect(getNameFormat(config, 'ReactPackage')).toBe('pascal-case'); + }); + + it('returns "pascal-case" when template is not in templateSettings', () => { + const config = { + templatesFolderPath: 'templates', + templateSettings: {}, + }; + + expect(getNameFormat(config, 'UnknownTemplate')).toBe('pascal-case'); + }); + + it('returns the correct format for each supported NameFormat', () => { + const formats = [ + 'pascal-case', + 'kebab-case', + 'camel-case', + 'snake-case', + ] as const; + + for (const nameFormat of formats) { + const config = { + templatesFolderPath: 'templates', + templateSettings: { MyTemplate: { nameFormat } }, + }; + expect(getNameFormat(config, 'MyTemplate')).toBe(nameFormat); + } + }); +}); diff --git a/src/__tests__/utils/formatName.test.ts b/src/__tests__/utils/formatName.test.ts new file mode 100644 index 0000000..9145839 --- /dev/null +++ b/src/__tests__/utils/formatName.test.ts @@ -0,0 +1,164 @@ +import { canBeFormatted, formatName } from '../../utils/formatName'; + +describe('formatName', () => { + describe('pascal-case', () => { + it('leaves PascalCase unchanged', () => { + expect(formatName('Button', 'pascal-case')).toBe('Button'); + expect(formatName('MyComponent', 'pascal-case')).toBe('MyComponent'); + }); + + it('converts lowercase to PascalCase', () => { + expect(formatName('button', 'pascal-case')).toBe('Button'); + }); + + it('converts kebab-case to PascalCase', () => { + expect(formatName('my-button', 'pascal-case')).toBe('MyButton'); + expect(formatName('my-custom-card', 'pascal-case')).toBe('MyCustomCard'); + }); + + it('converts camelCase to PascalCase', () => { + expect(formatName('myButton', 'pascal-case')).toBe('MyButton'); + }); + + it('converts snake_case to PascalCase', () => { + expect(formatName('my_button', 'pascal-case')).toBe('MyButton'); + }); + + it('returns empty string for empty input', () => { + expect(formatName('', 'pascal-case')).toBe(''); + }); + }); + + describe('kebab-case', () => { + it('leaves kebab-case unchanged', () => { + expect(formatName('my-button', 'kebab-case')).toBe('my-button'); + }); + + it('converts PascalCase to kebab-case', () => { + expect(formatName('MyButton', 'kebab-case')).toBe('my-button'); + expect(formatName('MyCustomCard', 'kebab-case')).toBe('my-custom-card'); + }); + + it('converts camelCase to kebab-case', () => { + expect(formatName('myButton', 'kebab-case')).toBe('my-button'); + }); + + it('converts snake_case to kebab-case', () => { + expect(formatName('my_button', 'kebab-case')).toBe('my-button'); + }); + + it('converts lowercase to kebab-case (single word)', () => { + expect(formatName('button', 'kebab-case')).toBe('button'); + }); + + it('returns empty string for empty input', () => { + expect(formatName('', 'kebab-case')).toBe(''); + }); + }); + + describe('camel-case', () => { + it('leaves camelCase unchanged', () => { + expect(formatName('myButton', 'camel-case')).toBe('myButton'); + }); + + it('converts PascalCase to camelCase', () => { + expect(formatName('MyButton', 'camel-case')).toBe('myButton'); + expect(formatName('MyCustomCard', 'camel-case')).toBe('myCustomCard'); + }); + + it('converts kebab-case to camelCase', () => { + expect(formatName('my-button', 'camel-case')).toBe('myButton'); + }); + + it('converts snake_case to camelCase', () => { + expect(formatName('my_button', 'camel-case')).toBe('myButton'); + }); + + it('converts lowercase to camelCase (single word)', () => { + expect(formatName('button', 'camel-case')).toBe('button'); + }); + + it('returns empty string for empty input', () => { + expect(formatName('', 'camel-case')).toBe(''); + }); + }); + + describe('snake-case', () => { + it('leaves snake_case unchanged', () => { + expect(formatName('my_button', 'snake-case')).toBe('my_button'); + }); + + it('converts PascalCase to snake_case', () => { + expect(formatName('MyButton', 'snake-case')).toBe('my_button'); + expect(formatName('MyCustomCard', 'snake-case')).toBe('my_custom_card'); + }); + + it('converts kebab-case to snake_case', () => { + expect(formatName('my-button', 'snake-case')).toBe('my_button'); + }); + + it('converts camelCase to snake_case', () => { + expect(formatName('myButton', 'snake-case')).toBe('my_button'); + }); + + it('converts lowercase to snake_case (single word)', () => { + expect(formatName('button', 'snake-case')).toBe('button'); + }); + + it('returns empty string for empty input', () => { + expect(formatName('', 'snake-case')).toBe(''); + }); + }); + + describe('cross-format consistency', () => { + it('all formats produce non-empty output for "my-package"', () => { + expect(formatName('my-package', 'pascal-case')).toBe('MyPackage'); + expect(formatName('my-package', 'kebab-case')).toBe('my-package'); + expect(formatName('my-package', 'camel-case')).toBe('myPackage'); + expect(formatName('my-package', 'snake-case')).toBe('my_package'); + }); + }); +}); + +describe('canBeFormatted', () => { + it('returns true for a name starting with a lowercase letter', () => { + expect(canBeFormatted('button')).toBe(true); + }); + + it('returns true for a name starting with an uppercase letter', () => { + expect(canBeFormatted('Button')).toBe(true); + }); + + it('returns true for a kebab-case name', () => { + expect(canBeFormatted('my-button')).toBe(true); + }); + + it('returns true for a snake_case name', () => { + expect(canBeFormatted('my_button')).toBe(true); + }); + + it('returns false for empty string', () => { + expect(canBeFormatted('')).toBe(false); + }); + + it('returns false for whitespace-only string', () => { + expect(canBeFormatted(' ')).toBe(false); + }); + + it('returns false for a name starting with a digit', () => { + expect(canBeFormatted('1button')).toBe(false); + expect(canBeFormatted('123')).toBe(false); + }); + + it('returns false for a name starting with a hyphen', () => { + expect(canBeFormatted('-button')).toBe(false); + }); + + it('returns false for a name starting with an underscore', () => { + expect(canBeFormatted('_button')).toBe(false); + }); + + it('trims leading whitespace before checking', () => { + expect(canBeFormatted(' button')).toBe(true); + }); +}); diff --git a/src/__tests__/utils/validation.test.ts b/src/__tests__/utils/validation.test.ts index d10c778..590532e 100644 --- a/src/__tests__/utils/validation.test.ts +++ b/src/__tests__/utils/validation.test.ts @@ -1,5 +1,6 @@ import { COMPONENT_NAME_ERROR, + canBeFormatted, formatToPascalCase, isValidComponentName, } from '../../utils/validation'; @@ -86,3 +87,23 @@ describe('COMPONENT_NAME_ERROR', () => { expect(COMPONENT_NAME_ERROR.length).toBeGreaterThan(0); }); }); + +describe('canBeFormatted', () => { + it('is re-exported from validation', () => { + expect(typeof canBeFormatted).toBe('function'); + }); + + it('returns true for valid starting characters', () => { + expect(canBeFormatted('button')).toBe(true); + expect(canBeFormatted('Button')).toBe(true); + expect(canBeFormatted('my-button')).toBe(true); + }); + + it('returns false for names starting with a digit', () => { + expect(canBeFormatted('1button')).toBe(false); + }); + + it('returns false for empty string', () => { + expect(canBeFormatted('')).toBe(false); + }); +}); diff --git a/src/commands/createComponent.ts b/src/commands/createComponent.ts index dcfd388..7f0b617 100644 --- a/src/commands/createComponent.ts +++ b/src/commands/createComponent.ts @@ -1,6 +1,6 @@ import * as path from 'node:path'; import * as vscode from 'vscode'; -import { readConfig } from '../helpers/config/readConfig'; +import { getNameFormat, readConfig } from '../helpers/config/readConfig'; import { listTemplates } from '../helpers/template/listTemplates'; import { processTemplateFolder } from '../helpers/template/processTemplateFolder'; import { getWorkspaceFolders } from '../helpers/vscode/getWorkspaceFolders'; @@ -11,11 +11,8 @@ import { showInfo } from '../helpers/vscode/showInfo'; import { createWorkspaceFolder } from '../helpers/vscode/workspace/createFolder'; import { workspaceFileExists } from '../helpers/vscode/workspace/fileExists'; import { writeWorkspaceFile } from '../helpers/vscode/workspace/writeFile'; -import { - COMPONENT_NAME_ERROR, - formatToPascalCase, - isValidComponentName, -} from '../utils/validation'; +import { formatName } from '../utils/formatName'; +import { COMPONENT_NAME_ERROR, canBeFormatted } from '../utils/validation'; const findWorkspaceFolderUri = ( clickedUri: vscode.Uri, @@ -64,10 +61,7 @@ export const createComponent = async ( const rawName = await promptInput({ prompt: 'Component name', placeholder: 'e.g. Button', - validateInput: v => - isValidComponentName(formatToPascalCase(v)) - ? undefined - : COMPONENT_NAME_ERROR, + validateInput: v => (canBeFormatted(v) ? undefined : COMPONENT_NAME_ERROR), }); if (!rawName) return; @@ -81,7 +75,10 @@ export const createComponent = async ( } if (!templateName) return; - const componentName = formatToPascalCase(rawName.trim()); + const componentName = formatName( + rawName.trim(), + getNameFormat(config, templateName), + ); const templateFolderUri = vscode.Uri.joinPath(templatesUri, templateName); const processedResult = await processTemplateFolder( diff --git a/src/commands/updateComponent.ts b/src/commands/updateComponent.ts index 00f6576..1df1354 100644 --- a/src/commands/updateComponent.ts +++ b/src/commands/updateComponent.ts @@ -1,6 +1,6 @@ import * as path from 'node:path'; import * as vscode from 'vscode'; -import { readConfig } from '../helpers/config/readConfig'; +import { getNameFormat, readConfig } from '../helpers/config/readConfig'; import { listTemplates } from '../helpers/template/listTemplates'; import { processTemplateFolder } from '../helpers/template/processTemplateFolder'; import { getWorkspaceFolders } from '../helpers/vscode/getWorkspaceFolders'; @@ -10,7 +10,8 @@ import { showInfo } from '../helpers/vscode/showInfo'; import { createWorkspaceFolder } from '../helpers/vscode/workspace/createFolder'; import { workspaceFileExists } from '../helpers/vscode/workspace/fileExists'; import { writeWorkspaceFile } from '../helpers/vscode/workspace/writeFile'; -import { formatToPascalCase, isValidComponentName } from '../utils/validation'; +import { formatName } from '../utils/formatName'; +import { canBeFormatted } from '../utils/validation'; const findWorkspaceFolderUri = ( clickedUri: vscode.Uri, @@ -30,8 +31,7 @@ export const updateComponent = async ( } const rawName = path.basename(clickedUri.fsPath); - const componentName = formatToPascalCase(rawName); - if (!isValidComponentName(componentName)) { + if (!canBeFormatted(rawName)) { showError( `'${rawName}' cannot be converted to a valid component name. Right-click directly on the component folder.`, ); @@ -75,6 +75,10 @@ export const updateComponent = async ( } if (!templateName) return; + const componentName = formatName( + rawName, + getNameFormat(config, templateName), + ); const templateFolderUri = vscode.Uri.joinPath(templatesUri, templateName); const processedResult = await processTemplateFolder( diff --git a/src/helpers/config/readConfig.ts b/src/helpers/config/readConfig.ts index adf2392..1046b34 100644 --- a/src/helpers/config/readConfig.ts +++ b/src/helpers/config/readConfig.ts @@ -1,9 +1,13 @@ import * as vscode from 'vscode'; import { + CONFIG_KEY_TEMPLATE_SETTINGS, CONFIG_KEY_TEMPLATES_FOLDER, CONFIG_SECTION, type CodebotConfig, + DEFAULT_NAME_FORMAT, DEFAULT_TEMPLATES_FOLDER, + type NameFormat, + type TemplateSettings, } from '../../types/Config'; export const readConfig = (scope?: vscode.Uri): CodebotConfig => { @@ -12,5 +16,15 @@ export const readConfig = (scope?: vscode.Uri): CodebotConfig => { templatesFolderPath: config.get(CONFIG_KEY_TEMPLATES_FOLDER) ?? DEFAULT_TEMPLATES_FOLDER, + templateSettings: + config.get>( + CONFIG_KEY_TEMPLATE_SETTINGS, + ) ?? {}, }; }; + +export const getNameFormat = ( + config: CodebotConfig, + templateName: string, +): NameFormat => + config.templateSettings[templateName]?.nameFormat ?? DEFAULT_NAME_FORMAT; diff --git a/src/types/Config.ts b/src/types/Config.ts index b591fd3..9d68255 100644 --- a/src/types/Config.ts +++ b/src/types/Config.ts @@ -1,7 +1,26 @@ +export type NameFormat = + | 'pascal-case' + | 'kebab-case' + | 'camel-case' + | 'snake-case'; + +export type TemplateSettings = { + readonly nameFormat?: NameFormat; +}; + export type CodebotConfig = { readonly templatesFolderPath: string; + readonly templateSettings: Record; }; export const DEFAULT_TEMPLATES_FOLDER = 'templates'; +export const DEFAULT_NAME_FORMAT: NameFormat = 'pascal-case'; +export const NAME_FORMATS: readonly NameFormat[] = [ + 'pascal-case', + 'kebab-case', + 'camel-case', + 'snake-case', +]; export const CONFIG_SECTION = 'codebot'; export const CONFIG_KEY_TEMPLATES_FOLDER = 'templatesFolderPath'; +export const CONFIG_KEY_TEMPLATE_SETTINGS = 'templateSettings'; diff --git a/src/utils/formatName.ts b/src/utils/formatName.ts new file mode 100644 index 0000000..cb5890f --- /dev/null +++ b/src/utils/formatName.ts @@ -0,0 +1,32 @@ +import type { NameFormat } from '../types/Config'; + +const splitWords = (input: string): string[] => + input + .trim() + .replace(/([a-z])([A-Z])/g, '$1 $2') + .split(/[^a-zA-Z0-9]+/) + .filter(Boolean); + +const capitalize = (word: string): string => + word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); + +export const formatName = (input: string, format: NameFormat): string => { + const words = splitWords(input); + if (words.length === 0) return ''; + + switch (format) { + case 'pascal-case': + return words.map(capitalize).join(''); + case 'kebab-case': + return words.map(w => w.toLowerCase()).join('-'); + case 'camel-case': + return [words[0].toLowerCase(), ...words.slice(1).map(capitalize)].join( + '', + ); + case 'snake-case': + return words.map(w => w.toLowerCase()).join('_'); + } +}; + +export const canBeFormatted = (input: string): boolean => + /^[a-zA-Z]/.test(input.trim()); diff --git a/src/utils/validation.ts b/src/utils/validation.ts index aa760ec..dbc9b2a 100644 --- a/src/utils/validation.ts +++ b/src/utils/validation.ts @@ -1,6 +1,7 @@ +import { canBeFormatted } from './formatName'; import { formatToPascalCase } from './formatToPascalCase'; -export { formatToPascalCase }; +export { canBeFormatted, formatToPascalCase }; const COMPONENT_NAME_RE = /^[A-Z][A-Za-z0-9]*$/; From 75aa6849194b3afb3c836c86cb2f2ac05801a2ad Mon Sep 17 00:00:00 2001 From: Douglas Gomes Date: Sat, 18 Apr 2026 14:15:31 -0300 Subject: [PATCH 15/17] fix: correct template syntax for name interpolation in App component - Updated the name interpolation syntax in App.tsx.hbs from {name} to {{name}} for proper rendering in the template. --- examples/ReactPackage/src/App.tsx.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ReactPackage/src/App.tsx.hbs b/examples/ReactPackage/src/App.tsx.hbs index 49cd4f5..5d2cba0 100644 --- a/examples/ReactPackage/src/App.tsx.hbs +++ b/examples/ReactPackage/src/App.tsx.hbs @@ -1,7 +1,7 @@ function App() { return (
-

{name}}

+

{{name}}

); } From 2241355cf765ee9bdce3eac9eb006f78024e5356 Mon Sep 17 00:00:00 2001 From: Douglas Gomes Date: Sat, 18 Apr 2026 14:24:38 -0300 Subject: [PATCH 16/17] feat: revamp README.md for improved clarity and presentation - Enhanced the README.md with a new layout, including a centered title and updated description for Codebot. - Added visual elements such as badges for marketplace visibility and installation metrics. - Expanded sections on template creation, quick start instructions, and naming conventions to provide clearer guidance for users. - Improved formatting and organization for better readability and user engagement. --- README.md | 319 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 289 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index c1f0dc9..ad887e9 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,145 @@ -

- - Codebot - -

Code generator by templates.

+ + Codebot +

-## About +

Codebot — Code generator by templates

-

Automates the creation of your boilerplate codes with custom templates.

+

+ Stop copying and pasting boilerplate. Define your file structure once as a template and generate it anywhere with two clicks. +

+ +

+ + VS Marketplace + + + Installs + + + Open VSX + + MIT License +

+ +--- + +## What it does -![Creating a component](/assets/codebot.gif) +Codebot reads a folder of [Handlebars](https://handlebarsjs.com/) (`.hbs`) template files you define once in your project, and generates all the files from that template every time you right-click a folder in the Explorer. -### Getting started with the templates creation: +- **Create Component** — generates a new folder with all files from the chosen template, with every `{{name}}` replaced by your component name. +- **Update Component** — adds any missing files from a template into an existing component folder, without overwriting files that already exist. -1. Creates a new folder in the root of the project. -2. Creates the templates files using [handlebarsjs](https://handlebarsjs.com/). -3. For each type of template should be created a sub folder inside the 'templates' folder, containing the necessary files to the component creation. See the structure example: +Templates can be as simple as a single `.tsx` file or as complex as a full package with nested directories — Codebot handles both. + +--- + +## Quick start + +**1. Create your templates folder** + +By default, Codebot looks for a `templates/` folder at the root of your workspace. Each subfolder inside it is a separate template. ``` -├─ src -├─ templates -│ ├─ ComponentSassTsx -│ │ ├─ ComponentSass.module.scss.hbs -│ │ ├─ ComponentSass.tsx.hbs -│ │ └─ index.tsx.hbs -│ └─ ComponentStyled -│ ├─ index.tsx.hbs -│ └─ styles.ts.hbs -└── workspace +your-project/ +└── templates/ + └── ReactComponent/ + ├── ReactComponent.tsx.hbs + └── index.tsx.hbs ``` -![Template example](/assets/componenthbsexample.png) +**2. Write your template files using `{{name}}`** + +`ReactComponent.tsx.hbs` +```tsx +import { ReactNode } from 'react'; + +export interface {{name}}Props { + children: ReactNode; +} + +export function {{name}}({ children }: {{name}}Props) { + return
{children}
; +} +``` + +`index.tsx.hbs` +```tsx +export * from './{{name}}'; +``` + +**3. Right-click any folder in the Explorer** + +Select **🤖 Create Component**, type the component name (e.g. `Button`), and Codebot creates: + +``` +src/components/ +└── Button/ + ├── Button.tsx + └── index.tsx +``` + +Every `{{name}}` in file names and file contents is replaced with the formatted component name. + +--- + +## Template file naming convention -### Using the extension: -As in the gif example, you can press the mouse right-click button and select between two options: -1. Create Component: generates all the boilerplate files from scratch -2. Update Component: this brings up the files that don't exist in the folder +Codebot renames template files to match the component name using two patterns: -### Changing the configurations +| Template filename | Generated filename (component: `Button`) | +|---|---| +| `ReactComponent.tsx.hbs` | `Button.tsx` | +| `ReactComponent.module.scss.hbs` | `Button.module.scss` | +| `Template.spec.tsx.hbs` | `Button.spec.tsx` | +| `index.tsx.hbs` | `index.tsx` *(unchanged — no match)* | -Open VS Code settings (`Cmd+,` / `Ctrl+,`) and search for **Codebot**, or add the following to your `.vscode/settings.json`: +Any filename that contains either the **template folder name** or the literal word **`Template`** will be renamed to the component name. + +--- + +## Template variables + +Only one variable is available inside `.hbs` template content: + +| Variable | Value | +|---|---| +| `{{name}}` | The formatted component name (e.g. `Button`, `my-package`, `myComponent`) | + +The formatting of `{{name}}` is controlled by the [`nameFormat`](#name-formats) setting. + +--- + +## Commands + +Both commands are available by **right-clicking a folder** in the VS Code Explorer. + +### Create Component + +> Right-click a folder → **🤖 Create Component** + +1. You are prompted for the component name. +2. If you have more than one template, you select which template to use. +3. Codebot creates a new subfolder named after the component and writes all template files inside it. +4. Files that already exist are skipped automatically. + +### Update Component + +> Right-click an existing component folder → **🤖 Update Component** + +Adds any template files that are missing from an existing component folder. The component name is derived from the folder you right-click. Existing files are never overwritten. + +--- + +## Configuration + +Open VS Code settings (`Cmd+,` / `Ctrl+,`) and search for **Codebot**, or add any of these keys to your `.vscode/settings.json`: + +### `codebot.templatesFolderPath` + +Path to the templates folder, relative to the workspace root. ```json { @@ -48,4 +147,164 @@ Open VS Code settings (`Cmd+,` / `Ctrl+,`) and search for **Codebot**, or add th } ``` -Link to the VSCode extensions marketplace +**Default:** `"templates"` + +--- + +### `codebot.templateSettings` + +Per-template settings. Keys must match template folder names exactly. + +```json +{ + "codebot.templateSettings": { + "ReactComponent": { + "nameFormat": "pascal-case" + }, + "ReactPackage": { + "nameFormat": "kebab-case" + } + } +} +``` + +**Default:** `{}` (all templates use `pascal-case`) + +--- + +## Name formats + +The `nameFormat` option controls how the value you type is transformed before being passed to `{{name}}` and used as the output folder/file name. + +All formats accept any input style — you can type `my button`, `my-button`, `MyButton`, or `myButton` and they all produce the same result. + +| Format | Output example | Use case | +|---|---|---| +| `pascal-case` *(default)* | `MyComponent` | React components, classes | +| `kebab-case` | `my-component` | npm packages, CSS classes | +| `camel-case` | `myComponent` | JavaScript functions, variables | +| `snake-case` | `my_component` | Python modules, database tables | + +**Example:** typing `"my button"` with each format: + +``` +pascal-case → MyButton +kebab-case → my-button +camel-case → myButton +snake-case → my_button +``` + +--- + +## Nested template directories + +Templates can contain subdirectories of any depth (up to 10 levels). The directory structure is preserved in the output. + +``` +templates/ +└── ReactPackage/ + ├── package.json.hbs + ├── tsconfig.json.hbs + ├── vite.config.ts.hbs + └── src/ + ├── App.tsx.hbs + ├── main.tsx.hbs + └── lib/ + └── main.tsx.hbs +``` + +Generates: + +``` +packages/ +└── my-package/ + ├── package.json + ├── tsconfig.json + ├── vite.config.ts + └── src/ + ├── App.tsx + ├── main.tsx + └── lib/ + └── main.tsx +``` + +--- + +## Examples + +The [`examples/`](https://github.com/douglasgomes98/codebot/tree/main/examples) folder in this repository contains ready-to-use templates you can copy into your project: + +| Template | Files generated | Best for | +|---|---|---| +| [`ComponentSass`](https://github.com/douglasgomes98/codebot/tree/main/examples/ComponentSass) | `.tsx`, `.module.scss`, `.spec.tsx`, `index.tsx` | React components with CSS Modules | +| [`ComponentTailwind`](https://github.com/douglasgomes98/codebot/tree/main/examples/ComponentTailwind) | `.tsx`, `.style.ts`, `index.tsx` | React components with Tailwind Variants | +| [`ComponentStyled`](https://github.com/douglasgomes98/codebot/tree/main/examples/ComponentSyled) | `index.tsx`, `styles.ts` | React components with Styled Components | +| [`StoriesTsx`](https://github.com/douglasgomes98/codebot/tree/main/examples/StoriesTsx) | `.stories.tsx` | Storybook story files | +| [`ReactPackage`](https://github.com/douglasgomes98/codebot/tree/main/examples/ReactPackage) | Full Vite + React package scaffold | Monorepo packages (`kebab-case`) | + +### ComponentSass example + +`ComponentSass.tsx.hbs` +```tsx +import { ReactNode } from 'react'; + +import styles from './{{name}}.module.scss'; + +export interface {{name}}Props { + children: ReactNode; +} + +export function {{name}}({ children }: {{name}}Props) { + return ( +
+ {children} +
+ ); +} +``` + +`index.tsx.hbs` +```tsx +export * from './{{name}}'; +``` + +### ReactPackage example (kebab-case) + +`package.json.hbs` +```json +{ + "name": "@myorg/{{name}}", + "version": "0.0.1", + "main": "./dist/index.js", + "types": "./dist/index.d.ts" +} +``` + +`.vscode/settings.json` +```json +{ + "codebot.templateSettings": { + "ReactPackage": { + "nameFormat": "kebab-case" + } + } +} +``` + +Typing `"my button"` generates a folder named `my-button` with `package.json` containing `"name": "@myorg/my-button"`. + +--- + +## Requirements + +- VS Code `1.100.0` or later +- Templates folder must exist in the workspace before using the extension + +--- + +## Links + +- [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=douglasgomes98.codebot) +- [Open VSX Registry](https://open-vsx.org/extension/douglasgomes98/codebot) +- [GitHub Repository](https://github.com/douglasgomes98/codebot) +- [Report an issue](https://github.com/douglasgomes98/codebot/issues) From a8eebd43dfddd90ab9bcbf224223d499f1223ddd Mon Sep 17 00:00:00 2001 From: Douglas Gomes Date: Sat, 18 Apr 2026 14:36:46 -0300 Subject: [PATCH 17/17] refactor: rename commands and update README for clarity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Changed command titles from "Create Component" to "✨ Build Template" and "Update Component" to "🔁 Update Template" in package.json. - Updated README.md to reflect new command names and improve user instructions. - Adjusted error messages in createComponent and updateComponent functions for consistency with new terminology. --- README.md | 14 +++++++------- package.json | 4 ++-- src/commands/createComponent.ts | 2 +- src/commands/updateComponent.ts | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index ad887e9..08178fc 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,8 @@ Codebot reads a folder of [Handlebars](https://handlebarsjs.com/) (`.hbs`) template files you define once in your project, and generates all the files from that template every time you right-click a folder in the Explorer. -- **Create Component** — generates a new folder with all files from the chosen template, with every `{{name}}` replaced by your component name. -- **Update Component** — adds any missing files from a template into an existing component folder, without overwriting files that already exist. +- **✨ Build Template** — generates a new folder with all files from the chosen template, with every `{{name}}` replaced by your component name. +- **🔁 Update Template** — adds any missing files from a template into an existing folder, without overwriting files that already exist. Templates can be as simple as a single `.tsx` file or as complex as a full package with nested directories — Codebot handles both. @@ -72,7 +72,7 @@ export * from './{{name}}'; **3. Right-click any folder in the Explorer** -Select **🤖 Create Component**, type the component name (e.g. `Button`), and Codebot creates: +Select **✨ Build Template**, type the component name (e.g. `Button`), and Codebot creates: ``` src/components/ @@ -116,18 +116,18 @@ The formatting of `{{name}}` is controlled by the [`nameFormat`](#name-formats) Both commands are available by **right-clicking a folder** in the VS Code Explorer. -### Create Component +### ✨ Build Template -> Right-click a folder → **🤖 Create Component** +> Right-click a folder → **✨ Build Template** 1. You are prompted for the component name. 2. If you have more than one template, you select which template to use. 3. Codebot creates a new subfolder named after the component and writes all template files inside it. 4. Files that already exist are skipped automatically. -### Update Component +### 🔁 Update Template -> Right-click an existing component folder → **🤖 Update Component** +> Right-click an existing folder → **🔁 Update Template** Adds any template files that are missing from an existing component folder. The component name is derived from the folder you right-click. Existing files are never overwritten. diff --git a/package.json b/package.json index ab092a8..e8d7610 100644 --- a/package.json +++ b/package.json @@ -58,11 +58,11 @@ "commands": [ { "command": "codebot.createComponent", - "title": "🤖 Create Component" + "title": "✨ Build Template" }, { "command": "codebot.updateComponent", - "title": "🤖 Update Component" + "title": "🔁 Update Template" } ], "menus": { diff --git a/src/commands/createComponent.ts b/src/commands/createComponent.ts index 7f0b617..115415b 100644 --- a/src/commands/createComponent.ts +++ b/src/commands/createComponent.ts @@ -27,7 +27,7 @@ export const createComponent = async ( clickedUri?: vscode.Uri, ): Promise => { if (!clickedUri) { - showError('Right-click a folder in the Explorer to create a component.'); + showError('Right-click a folder in the Explorer to build a template.'); return; } diff --git a/src/commands/updateComponent.ts b/src/commands/updateComponent.ts index 1df1354..5c17053 100644 --- a/src/commands/updateComponent.ts +++ b/src/commands/updateComponent.ts @@ -26,7 +26,7 @@ export const updateComponent = async ( clickedUri?: vscode.Uri, ): Promise => { if (!clickedUri) { - showError('Right-click a component folder in the Explorer to update it.'); + showError('Right-click a folder in the Explorer to update a template.'); return; }