diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index fac7890..a4260a7 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -118,35 +118,3 @@ jobs: await github.rest.issues.addLabels({ owner, repo, issue_number: pr, labels: [label], }); - - # ── 4. Post a helpful checklist comment on first-time PR open ───────────── - welcome-comment: - name: Post Contribution Checklist - runs-on: ubuntu-latest - if: github.event.action == 'opened' - steps: - - name: Post checklist comment - uses: actions/github-script@v9 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const author = context.payload.pull_request.user.login; - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.payload.pull_request.number, - body: `Thanks for the PR, @${author}! 🎉 - - Before this gets reviewed, please make sure the following are checked: - - - [ ] Tests pass locally (\`npm test\`) - - [ ] Type check passes (\`npm run typecheck\`) - - [ ] Lint passes (\`npm run lint\`) - - [ ] New features include tests - - [ ] Documentation updated if needed - - [ ] PR targets the \`dev\` branch (not \`main\`) - - > See [CONTRIBUTING.md](https://github.com/night-slayer18/leetcode-cli/blob/dev/CONTRIBUTING.md) for full guidelines. - - The CI pipeline will also run build, test, lint, and typecheck automatically.` - }); diff --git a/README.md b/README.md index dc2fe08..1f209e5 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ The CLI keeps command semantics the same and applies site-specific GraphQL queri | `pick-batch ` | Pick multiple problems | | `reset ` | Reset solution file to original stub | | `bookmark ` | Manage problem bookmarks | -| `note ` | Manage problem notes | +| `note ` | Manage problem notes | | `daily` | Show today's challenge | | `random` | Get a random problem | | `test ` | Test solution against sample cases | @@ -191,6 +191,9 @@ Reset an existing local solution file back to the original LeetCode stub. This o ```bash leetcode reset 1 leetcode reset two-sum + +# Reset for a specific language when multiple solutions exist +leetcode reset 1 --lang python3 ``` ### Test & Submit @@ -202,6 +205,10 @@ All formats work for both `test` and `submit`: leetcode test 20 leetcode submit 20 +# Filter by language when multiple solutions exist +leetcode test 20 --lang python3 +leetcode submit 20 --lang python3 + # Using filename leetcode test 20.valid-parentheses.java leetcode submit 20.valid-parentheses.java @@ -262,8 +269,9 @@ leetcode pick-batch 1 2 3 -l python3 leetcode bookmark add 1 leetcode bookmark list -# Keep personal notes +# Keep personal notes (by problem ID or name) leetcode note 1 edit +leetcode note two-sum edit ``` ### User Statistics @@ -360,6 +368,9 @@ leetcode reset two-sum # Save current approach leetcode snapshot save 1 "brute-force" +# Save for a specific language +leetcode snapshot save 1 "optimized" --lang python3 + # Try a new approach, then save leetcode snapshot save 1 "hash-map" @@ -379,6 +390,9 @@ leetcode snapshot restore 1 brute-force # Compare with last accepted submission leetcode diff 1 +# Compare for a specific language +leetcode diff 1 --lang python3 + # Show unified diff (line-by-line changes) leetcode diff 1 --unified diff --git a/docs/commands.md b/docs/commands.md index a9b1ce3..c78e60a 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -314,6 +314,10 @@ This command overwrites the existing file immediately. It preserves the existing - `` - Problem ID or slug +**Options**: + +- `-l, --lang ` - Only match solution files in this language (default: configured language) + **Examples**: ```bash @@ -322,6 +326,9 @@ leetcode reset 1 # By slug leetcode reset two-sum + +# Reset the Python solution when multiple languages exist +leetcode reset 1 -l python3 ``` --- @@ -338,6 +345,7 @@ Test solution against sample test cases. - `-c, --testcase ` - Custom test case input - `-V, --visualize` - Visual output for data structures +- `-l, --lang ` - Only match solution files in this language when testing by problem ID (default: configured language) **Three Ways to Test**: @@ -391,6 +399,10 @@ Submit solution to LeetCode. - `` - Problem ID, filename, or file path +**Options**: + +- `-l, --lang ` - Only match solution files in this language when submitting by problem ID (default: configured language) + **Three Ways to Submit**: ```bash @@ -664,6 +676,10 @@ Upload your current solution to the collaboration room. **Usage**: `leetcode collab sync` +**Options**: + +- `-l, --lang ` - Only match solution files in this language (default: configured language) + Reads your local solution file and syncs it to the cloud so your partner can see it. --- @@ -674,6 +690,10 @@ Compare your solution with your partner's solution. **Usage**: `leetcode collab compare` +**Options**: + +- `-l, --lang ` - Only match solution files in this language (default: configured language) + Displays both solutions sequentially with line numbers for easy comparison. --- @@ -709,12 +729,17 @@ Save current solution as a snapshot. - `` - Problem ID - `[name]` - Optional snapshot name (defaults to `snapshot-N`) +**Options**: + +- `-l, --lang ` - Only match solution files in this language (default: configured language) + **Examples**: ```bash leetcode snapshot save 1 "brute-force" leetcode snapshot save 1 "hash-map-approach" leetcode snapshot save 1 # auto-named "snapshot-1" +leetcode snapshot save 1 -l python3 ``` --- @@ -746,11 +771,16 @@ Restore a previously saved snapshot. Auto-creates backup of current code. - `` - Problem ID - `` - Snapshot ID or name +**Options**: + +- `-l, --lang ` - Only match solution files in this language (default: configured language) + **Examples**: ```bash leetcode snapshot restore 1 1 # By ID leetcode snapshot restore 1 brute-force # By name +leetcode snapshot restore 1 1 -l java ``` --- @@ -806,6 +836,7 @@ Compare your current solution with past submissions or other files. - `-s, --submission ` - Compare with specific submission ID - `-f, --file ` - Compare with a local file - `-u, --unified` - Show unified diff (line-by-line changes) +- `-l, --lang ` - Only match solution files in this language (default: configured language) **Default**: Shows both solutions one below the other with line numbers and summary. diff --git a/docs/releases.md b/docs/releases.md index 3a98131..c736a7c 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -1,5 +1,64 @@ # Release Notes +## v3.6.0 + +> **Release Date**: 2026-10-14 +> **Focus**: Target Language Option (`--lang`) for Solution Locators, Unified Problem Identifier Resolution, and Dependency Security Fixes + +### 🚀 Features + +#### Target Language Option for ID-Based Commands ([#56](https://github.com/night-slayer18/leetcode-cli/issues/56)) + +Added the `-l, --lang ` option across all commands that locate solution files by problem ID (`test`, `submit`, `diff`, `snapshot`, `collab`, and `reset`): + +- **Multi-language support**: If your workspace contains solutions in multiple languages (e.g. `1.two-sum.ts` and `1.two-sum.py`), `--lang` allows targeting a specific language solution without typing the full relative path. +- **Language validation**: Verifies user input against supported languages upfront, reporting friendly errors (e.g., `Unsupported language: foo`) before executing file lookups or network requests. +- **Aliases supported**: Supports standard aliases (e.g. `py` for `python3`, `ts` for `typescript`, `js` for `javascript`, `cpp` for `c++`, `cs` for `csharp`). + +```bash +# Test or submit a specific language implementation +leetcode test 1 --lang python3 +leetcode submit 1 --lang cpp + +# Compare, snapshot, or reset a specific language solution +leetcode diff 1 --lang java +leetcode snapshot save 1 "optimized" --lang python3 +leetcode reset 1 --lang typescript +``` + +#### Unified Problem Identifier Resolution ([#50](https://github.com/night-slayer18/leetcode-cli/pull/50)) + +Consolidated problem ID and title slug resolution into shared resolver utilities: + +- **Shared resolver**: Extracted `resolveProblem`, `resolveProblemIdentifier`, and `isProblemNotFoundError` in `src/utils/problem-resolver.ts`. +- **Eliminated raw Zod schema validation errors**: When querying an unknown or mistyped slug, commands previously threw `expected object, received null`. They now report a clean, user-friendly message (`Problem "" not found`). +- **Consistent behavior across commands**: Unified across `show`, `hint`, `submissions`, `timer`, `pick`, `notes`, and `reset`. + +#### Title Slug Support for Notes Command ([#50](https://github.com/night-slayer18/leetcode-cli/pull/50)) + +`leetcode notes ` now accepts a problem name (slug) in addition to numeric IDs: + +- Resolves the problem slug through the LeetCode API to associate the note with the problem's frontend ID. +- Avoids redundant API calls by passing the fetched problem details directly to note templates. + +```bash +leetcode notes two-sum +leetcode notes two-sum edit +``` + +### 🔒 Security + +- **Automated dependency audit fix**: Resolved two high-severity vulnerabilities (`http-cache-semantics` [GHSA-ch52-4w7c-c8xp](https://github.com/advisories/GHSA-ch52-4w7c-c8xp) and `source-map-js` [GHSA-68fv-2mgg-jv7q](https://github.com/advisories/GHSA-68fv-2mgg-jv7q)). + +### 🧪 Testing + +- Added unit tests for shared problem resolver in `src/__tests__/utils/problem-resolver.test.ts` (17 tests). +- Added unit tests for solution file lookup with language filter in `src/__tests__/utils/file-utils.test.ts`. +- Added regression tests for `--lang` support in `src/__tests__/commands/diff.test.ts`, `src/__tests__/commands/snapshot.test.ts`, and `src/__tests__/commands/reset.test.ts`. +- All 385 tests passing across 40 test suites. + +--- + ## v3.5.2 > **Release Date**: 2026-09-07 diff --git a/package-lock.json b/package-lock.json index 7dc1801..e9d6561 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@night-slayer18/leetcode-cli", - "version": "3.5.2", + "version": "3.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@night-slayer18/leetcode-cli", - "version": "3.5.2", + "version": "3.6.0", "license": "Apache-2.0", "dependencies": { "@supabase/supabase-js": "^2.90.1", @@ -134,6 +134,30 @@ "node": ">=18" } }, + "node_modules/@cacheable/memory": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz", + "integrity": "sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/utils": "^2.5.0", + "@keyv/bigmap": "^1.3.1", + "hookified": "^1.15.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.5.0.tgz", + "integrity": "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.5.1", + "keyv": "^5.6.0" + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -145,18 +169,18 @@ } }, "node_modules/@commitlint/cli": { - "version": "21.2.2", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-21.2.2.tgz", - "integrity": "sha512-a+6hQxIxnpdvSvS2apvttPNbEliYsVC3PqFYDiiB2kjbwIsQsj1urvQ4Tkf70pKYozPalKAuRQmm/GHwndduqA==", + "version": "21.2.3", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-21.2.3.tgz", + "integrity": "sha512-5yOX6IRjcrIReCxpyAd04hgI/yhUMpVBJ/ouwFV0wk9yx5+fYRDWZWJ5h55Tbedgfol+5m2mU5nqzbB8g3v0vQ==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/config-conventional": "^21.2.2", - "@commitlint/format": "^21.2.2", - "@commitlint/lint": "^21.2.2", - "@commitlint/load": "^21.2.2", - "@commitlint/read": "^21.2.1", - "@commitlint/types": "^21.2.0", + "@commitlint/config-conventional": "^21.2.3", + "@commitlint/format": "^21.2.3", + "@commitlint/lint": "^21.2.3", + "@commitlint/load": "^21.2.3", + "@commitlint/read": "^21.2.3", + "@commitlint/types": "^21.2.3", "tinyexec": "^1.0.0", "yargs": "^18.0.0" }, @@ -263,13 +287,13 @@ } }, "node_modules/@commitlint/config-conventional": { - "version": "21.2.2", - "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-21.2.2.tgz", - "integrity": "sha512-NxA37SZviusFUEYOQZ5hNnZ1h7O/KiemPkxjOlpzKJNnWxThiwc6/SaZhaPa8fyLvfRBAywhQhJJk8XESHWlpQ==", + "version": "21.2.3", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-21.2.3.tgz", + "integrity": "sha512-EP2n5DN2GwRWNVrLX0Jx3mpQlW5PfFi9HnD9OJ8hN/H0xFqpCFyHeGDhg+0P8/21iMMcdkcQgbNqmVklebqYsQ==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^21.2.0", + "@commitlint/types": "^21.2.3", "conventional-changelog-conventionalcommits": "^10.0.0" }, "engines": { @@ -277,13 +301,13 @@ } }, "node_modules/@commitlint/config-validator": { - "version": "21.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-21.2.0.tgz", - "integrity": "sha512-t7AzNHAKeIdo/3NRGwzpufKHsKkPHmFs/56N2Fnsh0/r0rGtnQzTxk6vnFgjaGr4hdSQKNB50/KAhR9Yk4LJKA==", + "version": "21.2.3", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-21.2.3.tgz", + "integrity": "sha512-JQ13v0GafSM4fBYX8UowSy++07Was20auq0NcTdnzSEq6/05Cq5GBUMIfKkqjzyBwC9sJFmPpKWgLLhH7cygkw==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^21.2.0", + "@commitlint/types": "^21.2.3", "ajv": "^8.11.0" }, "engines": { @@ -291,13 +315,13 @@ } }, "node_modules/@commitlint/ensure": { - "version": "21.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-21.2.0.tgz", - "integrity": "sha512-76IF9vDNS13lAzEEik9eKwzt8f9hYhWiwVXZ2AnyLCz5/f511FsEQ3pw1X3/zSQpdRLQU7i5qDMVKyXi1GWjSg==", + "version": "21.2.3", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-21.2.3.tgz", + "integrity": "sha512-gEjNKgRocbODI5/sMx2Qc9XOf2/zFTNV2aEK1QZ4gKDKxDC/VR7us3QtIX1C5d5xxHxx0I/tEbYbdSP1Am2LwA==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^21.2.0", + "@commitlint/types": "^21.2.3", "es-toolkit": "^1.46.0" }, "engines": { @@ -315,13 +339,13 @@ } }, "node_modules/@commitlint/format": { - "version": "21.2.2", - "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-21.2.2.tgz", - "integrity": "sha512-v6fvxZSc/AvVMROlr3H34+1766bZSYApRUSCAMjWamStPjKMvZ8GdvVA5YW/VQNgbFTmcMz6OYmSTJEvIjPrfA==", + "version": "21.2.3", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-21.2.3.tgz", + "integrity": "sha512-KKwCjXYqA8r295uhLnulDXL9yekdXmj2yp2Q0XsBPhxQ2iPuvRE8F43j7g7yexo69wTdmUQmFz15jpderluzUQ==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^21.2.0", + "@commitlint/types": "^21.2.3", "picocolors": "^1.1.1" }, "engines": { @@ -329,13 +353,13 @@ } }, "node_modules/@commitlint/is-ignored": { - "version": "21.2.2", - "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-21.2.2.tgz", - "integrity": "sha512-9UoKNgfFE3LU7FrzierCvk3CdDfMDeVGC86qZiT/n0TIjfq/dmZ9MHuXd45OTNRa26ZanmJRxEtmiXk/lEJihg==", + "version": "21.2.3", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-21.2.3.tgz", + "integrity": "sha512-Ng4nRj/LBAOvc+GQu7bsFW4/fL89LvZ14OOC/ERLbUpnG6QBwWRzY/1oXGwPs3C8YHWjJObin4kLYo2YeEn70w==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^21.2.0", + "@commitlint/types": "^21.2.3", "semver": "^7.6.0" }, "engines": { @@ -343,32 +367,32 @@ } }, "node_modules/@commitlint/lint": { - "version": "21.2.2", - "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-21.2.2.tgz", - "integrity": "sha512-Fy8JxEBzdmsYWFude/61GxXu5O+wEymwiRK2z9GL9R8mCsXphCoGxAFc5iHn5mjlfcSrhiiONE+ksf4KOjnaPg==", + "version": "21.2.3", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-21.2.3.tgz", + "integrity": "sha512-lUZcSuVLLEDkhge9ERk8Rp+FXQtrUhCK+h328BFnzKXrTuXGdqiYMEpTlNWdZaOrjyMhMJaz354ZaWEGJxcLeg==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/is-ignored": "^21.2.2", - "@commitlint/parse": "^21.2.2", - "@commitlint/rules": "^21.2.2", - "@commitlint/types": "^21.2.0" + "@commitlint/is-ignored": "^21.2.3", + "@commitlint/parse": "^21.2.3", + "@commitlint/rules": "^21.2.3", + "@commitlint/types": "^21.2.3" }, "engines": { "node": ">=22.12.0" } }, "node_modules/@commitlint/load": { - "version": "21.2.2", - "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-21.2.2.tgz", - "integrity": "sha512-0Tt6wDPX167cjKC5D4zhm0+20wJJG+TN/TKovMOspfSe78rOnKX+MNzlVNiu6HyQPZChPJ8QBH31MVt6Bb8fCg==", + "version": "21.2.3", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-21.2.3.tgz", + "integrity": "sha512-cDrL8lOo23Lk7knGzQdjbFcq7z2JgvjwV6PhbESVvEc+Fe21MeLjPKBJi8nKjg7j+jh79CVbVSUbZc104thXRw==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/config-validator": "^21.2.0", + "@commitlint/config-validator": "^21.2.3", "@commitlint/execute-rule": "^21.0.1", - "@commitlint/resolve-extends": "^21.2.2", - "@commitlint/types": "^21.2.0", + "@commitlint/resolve-extends": "^21.2.3", + "@commitlint/types": "^21.2.3", "cosmiconfig": "^9.0.1", "cosmiconfig-typescript-loader": "^6.1.0", "es-toolkit": "^1.46.0", @@ -390,13 +414,13 @@ } }, "node_modules/@commitlint/parse": { - "version": "21.2.2", - "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-21.2.2.tgz", - "integrity": "sha512-MEkobPfvRp+z06Wro8HMG1BDGHzZmj82A1LH1nWeG3ipHpg/x4m6v3wEDvMBIKjRFUnfR3nBeFs3MVCr7UdAmg==", + "version": "21.2.3", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-21.2.3.tgz", + "integrity": "sha512-Jt61QpF1xA88damCELe8I0FlDZPSpXNc8IhN7z5kZ0EcDYPjdZ+ZHPvsSXl69r2MIlLnNKeHhOn2cmVpziFE9w==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^21.2.0", + "@commitlint/types": "^21.2.3", "conventional-changelog-angular": "^9.0.0", "conventional-commits-parser": "^7.0.0" }, @@ -405,14 +429,14 @@ } }, "node_modules/@commitlint/read": { - "version": "21.2.1", - "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-21.2.1.tgz", - "integrity": "sha512-hUW7EJQnNTL0vPOmVMNK4CrnrNBN0nN+JJHReFkdHO5y4iyHeEmTBwuC15OCqUTjxWo7idnH1LftfpWVIaPWIA==", + "version": "21.2.3", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-21.2.3.tgz", + "integrity": "sha512-szAepuLA1vTTr7fuudEg7jYyVHudf+FH+vptU91bhxE1WnE8TKrfTAi44HVllz8XpI8Pvp4GjR/iP5Nshgh+og==", "dev": true, "license": "MIT", "dependencies": { "@commitlint/top-level": "^21.2.0", - "@commitlint/types": "^21.2.0", + "@commitlint/types": "^21.2.3", "@conventional-changelog/git-client": "^3.0.0", "tinyexec": "^1.0.0" }, @@ -421,9 +445,9 @@ } }, "node_modules/@commitlint/read/node_modules/tinyexec": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", - "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", "dev": true, "license": "MIT", "engines": { @@ -431,14 +455,14 @@ } }, "node_modules/@commitlint/resolve-extends": { - "version": "21.2.2", - "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-21.2.2.tgz", - "integrity": "sha512-RPkJ/IFi7sMUUVbZLqwWFtWw/zRDcfFsmrPSiTMrt5wb7AdxOr86EGQFvmGzef5QKV5IPBWWCujqVTw1RWX44A==", + "version": "21.2.3", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-21.2.3.tgz", + "integrity": "sha512-bqJdmAtXgAQel+l02uIsF7rHgKgx1AGZdGBjam7uK4XW54ZAQtB6XAIUXezYprQiCcRMpBh4fHs5JodXK4mVZA==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/config-validator": "^21.2.0", - "@commitlint/types": "^21.2.0", + "@commitlint/config-validator": "^21.2.3", + "@commitlint/types": "^21.2.3", "es-toolkit": "^1.46.0", "global-directory": "^5.0.0", "resolve-from": "^5.0.0" @@ -448,16 +472,16 @@ } }, "node_modules/@commitlint/rules": { - "version": "21.2.2", - "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-21.2.2.tgz", - "integrity": "sha512-eplQzyYkBjYB1HyyRj8hkcK11Y9DU9nuBz7uOKEd6NpE9NGDytLFCAnlRE+OoiK/5sHEJsaz2RGhuWBvYzIbNA==", + "version": "21.2.3", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-21.2.3.tgz", + "integrity": "sha512-5+YnI/LuTx3JDl9GjqfP2z9x96M8KKDrd+4jOel/T0L1BB3oAmBlSN8xppf63QsudiVEgR8C0gRoAMrPVrms6g==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/ensure": "^21.2.0", + "@commitlint/ensure": "^21.2.3", "@commitlint/message": "^21.2.0", "@commitlint/to-lines": "^21.0.1", - "@commitlint/types": "^21.2.0" + "@commitlint/types": "^21.2.3" }, "engines": { "node": ">=22.12.0" @@ -487,9 +511,9 @@ } }, "node_modules/@commitlint/types": { - "version": "21.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-21.2.0.tgz", - "integrity": "sha512-7zVFCDB2reMvJH5dmbKnOQPjZEvjdJTH8jc0U/PIPU1r3/+vf5pD1HlfitV2MWsWXrvu7u39iY1lyLUPOaN0Gw==", + "version": "21.2.3", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-21.2.3.tgz", + "integrity": "sha512-sGEA473TlCvhCGNoTBY9OXZVWdscIXdGt0dm8EpB+2MRfmt8LNh/X56P/T3nCtEB+eH5qGEa3odphSAoUxSfSQ==", "dev": true, "license": "MIT", "dependencies": { @@ -537,40 +561,6 @@ "node": ">=22" } }, - "node_modules/@emnapi/core": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", - "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", - "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", - "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", @@ -1094,9 +1084,9 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", - "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.3.tgz", + "integrity": "sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1608,45 +1598,60 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@keyv/bigmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", + "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.4.0", + "hookified": "^1.15.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "keyv": "^5.6.0" + } + }, "node_modules/@keyv/serialize": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", "license": "MIT" }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", - "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "node_modules/@oxc-project/types": { + "version": "0.148.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.148.0.tgz", + "integrity": "sha512-Nm4s/jB+4FpFsPhWGEC4h7rzksesmtnMXomo6rCMcg/b8zLQuOziRgkCS1fxDCXOlJB/6Q8oABOZ/OP6RIPj9A==", "dev": true, "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.3" - }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" + "url": "https://github.com/sponsors/oxc-project" } }, - "node_modules/@oxc-project/types": { - "version": "0.137.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.137.0.tgz", - "integrity": "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==", + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.7.tgz", + "integrity": "sha512-EypzgnYCwyVY4NDHKzGmNJT5b+XaQEBniHxsMdeIQLB/tcCzZnhqrzHpZFbX9iaxx+5RiB8caATBtfvZP7zVxQ==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.3.tgz", - "integrity": "sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.7.tgz", + "integrity": "sha512-l17HE9EweWaqJZhuUuNBN/FzM62xw+DECVnJyvMsxn8vJFAGLy5QfLDoYAcronkAN8VxKZHezDpulHDPx95vFw==", "cpu": [ "arm64" ], @@ -1661,9 +1666,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.3.tgz", - "integrity": "sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.7.tgz", + "integrity": "sha512-8ED8ELFvHXc6OCETIn4gXObPiaR6bckM/ipXtbzlPVDRMBfEGjCKgO90F9YtfdpDatVx/ZQw7aZ1vUMf/+T3Mw==", "cpu": [ "arm64" ], @@ -1678,9 +1683,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.3.tgz", - "integrity": "sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.7.tgz", + "integrity": "sha512-/WPripjtiAIZ2tWY7ddijORT0Ujg87wxWW/qcoFVCKAWVDPhtY0xr7Dj0M3GyNGz60jGwTElhro/mkF9dT7dDQ==", "cpu": [ "x64" ], @@ -1695,9 +1700,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.3.tgz", - "integrity": "sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.7.tgz", + "integrity": "sha512-14DI4NcqpvbICxSnGLx3PmtDaWqRP/KGSGb6C+JLLVPeZRl6dKdHba3pGsqT3vpdTqhEYIPG0MMQ8c0xYqoJxA==", "cpu": [ "x64" ], @@ -1712,9 +1717,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.3.tgz", - "integrity": "sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.7.tgz", + "integrity": "sha512-bxrWIRvHWQvbJwi+VIie/kDJmQxcNE6xxWwZdqF/ExVAigtHkv54WTLQPb+QsZdnFy18fg7JPfWGL0RH6vwIlQ==", "cpu": [ "arm" ], @@ -1729,9 +1734,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.3.tgz", - "integrity": "sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.7.tgz", + "integrity": "sha512-toOY2BChBZyuxU7OYX6Tn389di4IzAqPTycVcci0O7FSfBqzRB3RZn+K5Is6ANf4tmgRd/K1yZTsNTXbkXsnLg==", "cpu": [ "arm64" ], @@ -1746,9 +1751,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.3.tgz", - "integrity": "sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.7.tgz", + "integrity": "sha512-lAIXTH/aiLRLxsTgQvfhjo4K1ydWIp00+V0voOr9beb/9ZmkUFrSIb03dXNFRgMNvkE6oGsF10ioQ6UsI+vS5Q==", "cpu": [ "arm64" ], @@ -1763,9 +1768,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.3.tgz", - "integrity": "sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.7.tgz", + "integrity": "sha512-kdnwS28Pkenp/mZMRwjXXXwxQ7pIsm+bF919LUK93BOyhcLsrVKdP2p9fxpiPNPAbNuch8ypQt0pm2P2LYCAGg==", "cpu": [ "ppc64" ], @@ -1780,9 +1785,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.3.tgz", - "integrity": "sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.7.tgz", + "integrity": "sha512-516OdsyLdr5E65paF3yBF55t8mfm9+gmtCsK3xI7XKXIT7EfRlHhxL8K/NR6Hu8BWSgF5+1w74lTL0+nxcc8Qw==", "cpu": [ "s390x" ], @@ -1797,9 +1802,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.3.tgz", - "integrity": "sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.7.tgz", + "integrity": "sha512-r8/z8n7GFaYRln3xmP1Cxy0HH/HLM0uBUPkEuSVEfKGDA89M0FsZRZJRSwe/tJjRx+fpH/gjorfhB8tmEbSFLA==", "cpu": [ "x64" ], @@ -1814,9 +1819,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.3.tgz", - "integrity": "sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.7.tgz", + "integrity": "sha512-pAsE8iiDxUg1xBqdhrTfg45AVDVpirjz00sblEYClGNNcMnDb+e8beQgqIAw6LvauX/APvgxUnwrgun/YYGBhw==", "cpu": [ "x64" ], @@ -1831,9 +1836,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.3.tgz", - "integrity": "sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.7.tgz", + "integrity": "sha512-lTcIYmmnQQA8Or/2DatS6oSqcdLHvendjS+zLu+FwgToynWMRSmQdpM65fTANJgIS4mjbMOo5KT2lnT9SAb96w==", "cpu": [ "arm64" ], @@ -1847,29 +1852,10 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.3.tgz", - "integrity": "sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.11.1", - "@emnapi/runtime": "1.11.1", - "@napi-rs/wasm-runtime": "^1.1.6" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.3.tgz", - "integrity": "sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.7.tgz", + "integrity": "sha512-e3Gu3WxbNk/UqQhxqU7YIYO+9ZBvWNz3U+h/qRFosscMFzdRPbXYSaSWgSnklv2fz1TgzBTcti2z35c/7irsHw==", "cpu": [ "arm64" ], @@ -1884,9 +1870,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.3.tgz", - "integrity": "sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.7.tgz", + "integrity": "sha512-W/jg5qoRSqjsEv0+dZi4e687mcHqmVuU0P4fK6qS/xjetW2Gmc1W8j//z5nAeNcC8Ttm0hV46IjcYeuVwYhuiw==", "cpu": [ "x64" ], @@ -2312,9 +2298,9 @@ "license": "MIT" }, "node_modules/@supabase/auth-js": { - "version": "2.115.0", - "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.115.0.tgz", - "integrity": "sha512-YNQlQWm1H0gsXHSY8Jd/xepBhjO0Zhwx04iW17A83/joQ5kFiUin6iPj9s9kZZvupnwLjXVP/diTFiLz2jUbwQ==", + "version": "2.117.2", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.117.2.tgz", + "integrity": "sha512-Z3WnGwrphYZubrLbxp5Iv0tLA9A5GhvKzJ/ZXglxqwd2QEH2R4dXRltjVJ8sIn4xEW2BSFIGT0yALVJIgZeDYw==", "license": "MIT", "dependencies": { "tslib": "2.8.1" @@ -2324,9 +2310,9 @@ } }, "node_modules/@supabase/functions-js": { - "version": "2.115.0", - "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.115.0.tgz", - "integrity": "sha512-p97V6/YFcdp+zblFDVJaE8f9rGKTNz0PRzyJ2d1w/EYIU5lwidKuc3l/wM+u27ACmAC62albvoGiUGzLPSg7Aw==", + "version": "2.117.2", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.117.2.tgz", + "integrity": "sha512-6DT4ZIjmZxa9ANKaBIrjc86AyKrX8M376ynvNeOBBVad3eP7x3UHqWfDR2ynFkHGzCuOpr1A+kObmnahxuwuog==", "license": "MIT", "dependencies": { "tslib": "2.8.1" @@ -2342,9 +2328,9 @@ "license": "MIT" }, "node_modules/@supabase/postgrest-js": { - "version": "2.115.0", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.115.0.tgz", - "integrity": "sha512-DdERcurLh5t84pgSywDg2LjLR5le7XAzl52/iQhC7FdboWDqGfDPMfaWJV3MHvhyxSlSNzbpSQG+RiQOBSn/4w==", + "version": "2.117.2", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.117.2.tgz", + "integrity": "sha512-V1Qhn+M8xzJqCasOxHZ2KG19Fj39PxU7weEAmOK65/KTsWCRXboB4hcQwFvxRvM7ZikeP9IZWNvQkfjvSxzYFw==", "license": "MIT", "dependencies": { "tslib": "2.8.1" @@ -2354,9 +2340,9 @@ } }, "node_modules/@supabase/realtime-js": { - "version": "2.115.0", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.115.0.tgz", - "integrity": "sha512-5HyBkvlA/IUV2v8jX3uLTdy15jmTPRVXwXKoxvH2SKw5jZMNURxCxt+VpCEukscXQlY7pUpH8Cy4NH6io4iaRw==", + "version": "2.117.2", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.117.2.tgz", + "integrity": "sha512-lYXSAIg3eAKA58riUED6Vb+TCzF8jtx18uOoiCaVJ7ZMre6FWBpwB1MPeW+B6vykJT/hPdiuqwcSB7ILMS21ew==", "license": "MIT", "dependencies": { "@supabase/phoenix": "0.4.5", @@ -2367,9 +2353,9 @@ } }, "node_modules/@supabase/storage-js": { - "version": "2.115.0", - "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.115.0.tgz", - "integrity": "sha512-dLyIxzbO+MCcKHhcce8rVUCQX1iyqXqQ8ytgkOVYJ7D+Zp0qKylPtQH3hamgxrGSxtDjaw47Urpzw2iK9PsKdA==", + "version": "2.117.2", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.117.2.tgz", + "integrity": "sha512-8gAJoVaxZa/War2kFRfJMxGk4M190Q7lJ70BofwGlRxU/u9pIodd6XyTvmXkxOKXWuIYtkDaYPZrIAUt1T/7FQ==", "license": "MIT", "dependencies": { "iceberg-js": "^0.8.1", @@ -2380,16 +2366,16 @@ } }, "node_modules/@supabase/supabase-js": { - "version": "2.115.0", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.115.0.tgz", - "integrity": "sha512-PYJSxtCo37R7tTZW6pAqsxUeSx/dlhA7zn8RzKEUSCqyTxCUhG+iHrDTb04UyVBYbttaUbhwkNTvb8h5HW+uZA==", + "version": "2.117.2", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.117.2.tgz", + "integrity": "sha512-eSG2VKnHR+Clp1PmidZ1/weJ8PJwoybjva3L2GgKqFG4YDS1Iqmc61psKGZP5xw6OMT2O7ZorPR42PY6q1BOXg==", "license": "MIT", "dependencies": { - "@supabase/auth-js": "2.115.0", - "@supabase/functions-js": "2.115.0", - "@supabase/postgrest-js": "2.115.0", - "@supabase/realtime-js": "2.115.0", - "@supabase/storage-js": "2.115.0" + "@supabase/auth-js": "2.117.2", + "@supabase/functions-js": "2.117.2", + "@supabase/postgrest-js": "2.117.2", + "@supabase/realtime-js": "2.117.2", + "@supabase/storage-js": "2.117.2" }, "engines": { "node": ">=22.0.0" @@ -2403,17 +2389,6 @@ } } }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", - "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", @@ -2478,9 +2453,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.19.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz", - "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==", + "version": "22.20.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.5.tgz", + "integrity": "sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==", "devOptional": true, "license": "MIT", "dependencies": { @@ -2505,17 +2480,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz", - "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==", + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.71.0.tgz", + "integrity": "sha512-pqcS9c1HxZTHt7End4nXqd0s5lJrrFzrgCkKFJrsbUnaL6M3+6oBFZaslg6Gjsl3argl2DDRFROnXARaZ2e4Nw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.65.0", - "@typescript-eslint/type-utils": "8.65.0", - "@typescript-eslint/utils": "8.65.0", - "@typescript-eslint/visitor-keys": "8.65.0", + "@typescript-eslint/scope-manager": "8.71.0", + "@typescript-eslint/type-utils": "8.71.0", + "@typescript-eslint/utils": "8.71.0", + "@typescript-eslint/visitor-keys": "8.71.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -2528,22 +2503,22 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.65.0", + "@typescript-eslint/parser": "^8.71.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.65.0.tgz", - "integrity": "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==", + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.71.0.tgz", + "integrity": "sha512-CG4nPk1f2zc8yw4pALqHsFYH2hdo+h1T9daSp21+Hnxi9LOE3GT9hAfTKJCBXVNM2GmYs1eMEP615wPoeOgk3A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.65.0", - "@typescript-eslint/types": "8.65.0", - "@typescript-eslint/typescript-estree": "8.65.0", - "@typescript-eslint/visitor-keys": "8.65.0", + "@typescript-eslint/scope-manager": "8.71.0", + "@typescript-eslint/types": "8.71.0", + "@typescript-eslint/typescript-estree": "8.71.0", + "@typescript-eslint/visitor-keys": "8.71.0", "debug": "^4.4.3" }, "engines": { @@ -2559,14 +2534,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", - "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.71.0.tgz", + "integrity": "sha512-aABjw5rjBacYONVPaPiWOCjJu0vEF4a25iQuodlmQYL1trtLZ0X/y+2Vzl3BKI1odM4LnwLE1oUDXYp1wzx1TQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.65.0", - "@typescript-eslint/types": "^8.65.0", + "@typescript-eslint/tsconfig-utils": "^8.71.0", + "@typescript-eslint/types": "^8.71.0", "debug": "^4.4.3" }, "engines": { @@ -2581,14 +2556,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", - "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.71.0.tgz", + "integrity": "sha512-gWF0BhUcnjZxSpLE8ngS/59n2SB0J3YqRxvX1+2aoRJk9hNtHSLOV+TcarFiOr5ipXm3yc1QrI4c9YZc8zyCxw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.65.0", - "@typescript-eslint/visitor-keys": "8.65.0" + "@typescript-eslint/types": "8.71.0", + "@typescript-eslint/visitor-keys": "8.71.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2599,9 +2574,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", - "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.71.0.tgz", + "integrity": "sha512-Z1UlWHADEK2Mlb9NpWfDeSjqoZ5EyrOv4R3eQpbkzqn/EwaIdOpXXupEA1+0ZIOSJSZZDBHG0BrQyN8zUG6Pwg==", "dev": true, "license": "MIT", "engines": { @@ -2616,15 +2591,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz", - "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==", + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.71.0.tgz", + "integrity": "sha512-i8uO1qbdxeKgRnS5sCRt6On3/nfo2d2DwQe3Yvjx543zLy7r8ySqRuPPiIIXAhS03U0v5NfAFx+rUgxFzKKwNw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.65.0", - "@typescript-eslint/typescript-estree": "8.65.0", - "@typescript-eslint/utils": "8.65.0", + "@typescript-eslint/types": "8.71.0", + "@typescript-eslint/typescript-estree": "8.71.0", + "@typescript-eslint/utils": "8.71.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -2641,9 +2616,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", - "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.71.0.tgz", + "integrity": "sha512-cJ4OoxPGWvFnBTnSZyaU+qJzGTqPTGJY+gDchj6cRyLRdmIdt4rcsE4twj+zPfrNiWuVi38wijHzShL++Z9atQ==", "dev": true, "license": "MIT", "engines": { @@ -2655,16 +2630,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", - "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.71.0.tgz", + "integrity": "sha512-PEEF4G5sLLWAS5BpPrUvms4ySZkiBQQZM4z+3ReI46axK5Vqr/vXBQatJQIZZOYdGyPUAKTtsrWzpqKuU+3DEw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.65.0", - "@typescript-eslint/tsconfig-utils": "8.65.0", - "@typescript-eslint/types": "8.65.0", - "@typescript-eslint/visitor-keys": "8.65.0", + "@typescript-eslint/project-service": "8.71.0", + "@typescript-eslint/tsconfig-utils": "8.71.0", + "@typescript-eslint/types": "8.71.0", + "@typescript-eslint/visitor-keys": "8.71.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -2683,16 +2658,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", - "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.71.0.tgz", + "integrity": "sha512-pKR/tEMVrXZG23UFKUn5BQf3zfmfk7KQceI2cGzywZ5nxM5Eu3hEJU1utjWzydtzBbcJAQhHN8iPCxobHpPcZQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.65.0", - "@typescript-eslint/types": "8.65.0", - "@typescript-eslint/typescript-estree": "8.65.0" + "@typescript-eslint/scope-manager": "8.71.0", + "@typescript-eslint/types": "8.71.0", + "@typescript-eslint/typescript-estree": "8.71.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2707,13 +2682,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", - "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.71.0.tgz", + "integrity": "sha512-8eQ9R218XORK+KLosnf4bu/QsUXvUyVwTbArg7/0NMB1Pu87OJKvj4nhFblkYE8gQV73mW1dx1ptlPCkwRGa7A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/types": "8.71.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -2738,14 +2713,14 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.9.tgz", - "integrity": "sha512-G9/lgqibheLVBDRuya45EbsEXTYcWoSG+TLg7i2axuzx0Eq62eXn+aWXyaVdV5vKvFSWd6ywcX8hA7la9Pvu8g==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.11.tgz", + "integrity": "sha512-8MVGEFnJIcdGjcbfKmeq8z0pZHH0JlVtoVZH9Q/qwUp6wyFnEJUBMrw9DCaj+ra3vShGmhavjalMIhPNxZAUcw==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.9", + "@vitest/utils": "4.1.11", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", @@ -2759,8 +2734,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.1.9", - "vitest": "4.1.9" + "@vitest/browser": "4.1.11", + "vitest": "4.1.11" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -2769,16 +2744,16 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.9.tgz", - "integrity": "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz", + "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.9", - "@vitest/utils": "4.1.9", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -2787,13 +2762,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.9.tgz", - "integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz", + "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.9", + "@vitest/spy": "4.1.11", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -2814,9 +2789,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.9.tgz", - "integrity": "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz", + "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==", "dev": true, "license": "MIT", "dependencies": { @@ -2827,13 +2802,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.9.tgz", - "integrity": "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz", + "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.9", + "@vitest/utils": "4.1.11", "pathe": "^2.0.3" }, "funding": { @@ -2841,14 +2816,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.9.tgz", - "integrity": "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz", + "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.9", - "@vitest/utils": "4.1.9", + "@vitest/pretty-format": "4.1.11", + "@vitest/utils": "4.1.11", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -2857,9 +2832,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.9.tgz", - "integrity": "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz", + "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==", "dev": true, "license": "MIT", "funding": { @@ -2867,13 +2842,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.9.tgz", - "integrity": "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz", + "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.9", + "@vitest/pretty-format": "4.1.11", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -2991,9 +2966,9 @@ "license": "Python-2.0" }, "node_modules/argue-cli": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/argue-cli/-/argue-cli-3.1.0.tgz", - "integrity": "sha512-DhBpBfXL4SS2uC0N922MMajKR3CdrTG0u2or1PNYgXMsrSzViJrbtvT0nCLlLGUI0plam/ZZCs7aAauHtW9thw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/argue-cli/-/argue-cli-3.2.0.tgz", + "integrity": "sha512-VipTB0gXgGIFO2Rg9yEVN5wLt2AurJcZqDbgmYSwPwsykhLrQhs240/bfceev4w68lI8JshoOJIk9uW7tFzROw==", "dev": true, "license": "MIT", "engines": { @@ -3067,9 +3042,9 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3166,6 +3141,20 @@ "node": ">=8" } }, + "node_modules/cacheable": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz", + "integrity": "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/memory": "^2.2.0", + "@cacheable/utils": "^2.5.0", + "hookified": "^1.15.0", + "keyv": "^5.6.0", + "qified": "^0.10.1" + } + }, "node_modules/cacheable-lookup": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", @@ -3193,15 +3182,6 @@ "node": ">=18" } }, - "node_modules/cacheable-request/node_modules/keyv": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", - "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", - "license": "MIT", - "dependencies": { - "@keyv/serialize": "^1.1.1" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -3825,9 +3805,9 @@ "license": "MIT" }, "node_modules/es-toolkit": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.51.0.tgz", - "integrity": "sha512-zC2lQGkM7QX+Gm6iM3+WIdZJzthsEd14LvRNJneSO2hzyz/zNBENR8+YXWo1cKxgPBtV6ksPYHELbcwBRzmdCw==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.52.0.tgz", + "integrity": "sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==", "dev": true, "license": "MIT", "workspaces": [ @@ -3902,9 +3882,9 @@ } }, "node_modules/eslint": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.0.tgz", - "integrity": "sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==", + "version": "10.12.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.12.0.tgz", + "integrity": "sha512-npHjrHb2o4ehH3mE+YuxABR+0gyb0aVWCIolgJDArwCiHEIsonBm3ZKjel5b0U5+a+MbEvIyRCGCzD3eKKK2NA==", "dev": true, "license": "MIT", "workspaces": [ @@ -3916,7 +3896,7 @@ "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.7.0", "@eslint/core": "^1.2.1", - "@eslint/plugin-kit": "^0.7.2", + "@eslint/plugin-kit": "^0.7.3", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -3931,7 +3911,7 @@ "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", + "file-entry-cache": "11.1.5 || >11.1.6 <12", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", @@ -4166,9 +4146,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", - "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", "funding": [ { "type": "github", @@ -4200,16 +4180,13 @@ } }, "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.5.tgz", + "integrity": "sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" + "flat-cache": "^6.1.23" } }, "node_modules/find-up": { @@ -4242,23 +4219,21 @@ } }, "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "version": "6.1.23", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.23.tgz", + "integrity": "sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==", "dev": true, "license": "MIT", "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" + "cacheable": "^2.5.0", + "flatted": "^3.4.2", + "hookified": "^1.15.0" } }, "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", "dev": true, "license": "ISC" }, @@ -4400,15 +4375,6 @@ "url": "https://github.com/sindresorhus/got?sponsor=1" } }, - "node_modules/got/node_modules/keyv": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", - "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", - "license": "MIT", - "dependencies": { - "@keyv/serialize": "^1.1.1" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -4418,6 +4384,19 @@ "node": ">=8" } }, + "node_modules/hashery": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", + "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^1.15.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/highlight.js": { "version": "10.7.3", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", @@ -4427,6 +4406,13 @@ "node": "*" } }, + "node_modules/hookified": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", + "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", + "dev": true, + "license": "MIT" + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -4435,9 +4421,9 @@ "license": "MIT" }, "node_modules/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.3.0.tgz", + "integrity": "sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA==", "license": "BSD-2-Clause" }, "node_modules/http2-wrapper": { @@ -4834,13 +4820,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, "node_modules/json-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", @@ -4879,13 +4858,12 @@ } }, "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", "license": "MIT", "dependencies": { - "json-buffer": "3.0.1" + "@keyv/serialize": "^1.1.1" } }, "node_modules/levn": { @@ -4903,9 +4881,9 @@ } }, "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -4919,23 +4897,23 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" } }, "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", "cpu": [ "arm64" ], @@ -4954,9 +4932,9 @@ } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", "cpu": [ "arm64" ], @@ -4975,9 +4953,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", "cpu": [ "x64" ], @@ -4996,9 +4974,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", "cpu": [ "x64" ], @@ -5017,9 +4995,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", "cpu": [ "arm" ], @@ -5038,9 +5016,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", "cpu": [ "arm64" ], @@ -5059,9 +5037,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", "cpu": [ "arm64" ], @@ -5080,9 +5058,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", "cpu": [ "x64" ], @@ -5101,9 +5079,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", "cpu": [ "x64" ], @@ -5122,9 +5100,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", "cpu": [ "arm64" ], @@ -5143,9 +5121,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", "cpu": [ "x64" ], @@ -5184,15 +5162,15 @@ "license": "MIT" }, "node_modules/lint-staged": { - "version": "17.3.0", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-17.3.0.tgz", - "integrity": "sha512-woZS3vNe3UKqBaLPvbLOtKRY4tLANpWQhom12MGWqC8Mh1lCOO+WgSwmX2amjJAqTY9BkXYW87fCUH5H9Ph6xw==", + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-17.6.0.tgz", + "integrity": "sha512-nOhBfYkZEwkFtNzD5pK9s9LkcWoxJ+whLzEnhqi9nl7zTB5n7tg8+9iYgMGx2Wm3VlLu8cbNj0RACzY+nWi5iQ==", "dev": true, "license": "MIT", "dependencies": { - "picomatch": "^4.0.5", + "picomatch": "^4.0.7", "string-argv": "^0.3.2", - "tinyexec": "^1.2.4" + "tinyexec": "^1.3.1" }, "bin": { "lint-staged": "bin/lint-staged.js" @@ -5204,13 +5182,13 @@ "url": "https://opencollective.com/lint-staged" }, "optionalDependencies": { - "yaml": "^2.9.0" + "yaml": "^2.9.1" } }, "node_modules/lint-staged/node_modules/tinyexec": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", - "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", "dev": true, "license": "MIT", "engines": { @@ -5571,9 +5549,9 @@ } }, "node_modules/open": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/open/-/open-11.0.2.tgz", - "integrity": "sha512-RWqF+pBSkqecEvCKOn8QYhaNdRMJDZRIrlS/7rTDdLHaPcfXGCZ/h8zb413NfvdeAV0MR7T1yJcA34/q+CSm1Q==", + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.4.tgz", + "integrity": "sha512-++Zlftm0kVLPmzC06t6epuWmcRMDbI4z5P3NNX979WA/k23+NtSOynEGzsVfZwguKw2mi5umVgnBlJQMwRz4Pg==", "license": "MIT", "dependencies": { "default-browser": "^5.5.1", @@ -5810,9 +5788,9 @@ } }, "node_modules/postcss": { - "version": "8.5.25", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", - "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", "dev": true, "funding": [ { @@ -5830,7 +5808,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.16", + "nanoid": "^3.3.18", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -5931,9 +5909,9 @@ } }, "node_modules/prettier": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", - "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.9.tgz", + "integrity": "sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==", "dev": true, "license": "MIT", "bin": { @@ -5966,6 +5944,26 @@ "node": ">=6" } }, + "node_modules/qified": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/qified/-/qified-0.10.1.tgz", + "integrity": "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^2.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/qified/node_modules/hookified": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.2.0.tgz", + "integrity": "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==", + "dev": true, + "license": "MIT" + }, "node_modules/quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", @@ -6108,13 +6106,13 @@ } }, "node_modules/rolldown": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.3.tgz", - "integrity": "sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.7.tgz", + "integrity": "sha512-g0EtLvBjTUB7jhyV0S/TCup3v/XSVl45vUIGbOGU4QPiyjTenCe4mKuFvW9fEgYmS2Fo42AUssRmNuMziXdrig==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.137.0", + "@oxc-project/types": "=0.148.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -6124,21 +6122,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.1.3", - "@rolldown/binding-darwin-arm64": "1.1.3", - "@rolldown/binding-darwin-x64": "1.1.3", - "@rolldown/binding-freebsd-x64": "1.1.3", - "@rolldown/binding-linux-arm-gnueabihf": "1.1.3", - "@rolldown/binding-linux-arm64-gnu": "1.1.3", - "@rolldown/binding-linux-arm64-musl": "1.1.3", - "@rolldown/binding-linux-ppc64-gnu": "1.1.3", - "@rolldown/binding-linux-s390x-gnu": "1.1.3", - "@rolldown/binding-linux-x64-gnu": "1.1.3", - "@rolldown/binding-linux-x64-musl": "1.1.3", - "@rolldown/binding-openharmony-arm64": "1.1.3", - "@rolldown/binding-wasm32-wasi": "1.1.3", - "@rolldown/binding-win32-arm64-msvc": "1.1.3", - "@rolldown/binding-win32-x64-msvc": "1.1.3" + "@rolldown/binding-android-arm-eabi": "1.2.7", + "@rolldown/binding-android-arm64": "1.2.7", + "@rolldown/binding-darwin-arm64": "1.2.7", + "@rolldown/binding-darwin-x64": "1.2.7", + "@rolldown/binding-freebsd-x64": "1.2.7", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.7", + "@rolldown/binding-linux-arm64-gnu": "1.2.7", + "@rolldown/binding-linux-arm64-musl": "1.2.7", + "@rolldown/binding-linux-ppc64-gnu": "1.2.7", + "@rolldown/binding-linux-s390x-gnu": "1.2.7", + "@rolldown/binding-linux-x64-gnu": "1.2.7", + "@rolldown/binding-linux-x64-musl": "1.2.7", + "@rolldown/binding-openharmony-arm64": "1.2.7", + "@rolldown/binding-win32-arm64-msvc": "1.2.7", + "@rolldown/binding-win32-x64-msvc": "1.2.7" } }, "node_modules/rollup": { @@ -6379,9 +6377,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -6856,16 +6854,16 @@ "license": "MIT" }, "node_modules/vite": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.0.tgz", - "integrity": "sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==", + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz", + "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==", "dev": true, "license": "MIT", "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.15", - "rolldown": "~1.1.2", + "lightningcss": "^1.33.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.26", + "rolldown": "~1.2.4", "tinyglobby": "^0.2.17" }, "bin": { @@ -6882,7 +6880,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.3.0", + "@vitejs/devtools": "^0.4.0 || ^0.5.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -6934,19 +6932,19 @@ } }, "node_modules/vitest": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.9.tgz", - "integrity": "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz", + "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.9", - "@vitest/mocker": "4.1.9", - "@vitest/pretty-format": "4.1.9", - "@vitest/runner": "4.1.9", - "@vitest/snapshot": "4.1.9", - "@vitest/spy": "4.1.9", - "@vitest/utils": "4.1.9", + "@vitest/expect": "4.1.11", + "@vitest/mocker": "4.1.11", + "@vitest/pretty-format": "4.1.11", + "@vitest/runner": "4.1.11", + "@vitest/snapshot": "4.1.11", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -6974,12 +6972,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.9", - "@vitest/browser-preview": "4.1.9", - "@vitest/browser-webdriverio": "4.1.9", - "@vitest/coverage-istanbul": "4.1.9", - "@vitest/coverage-v8": "4.1.9", - "@vitest/ui": "4.1.9", + "@vitest/browser-playwright": "4.1.11", + "@vitest/browser-preview": "4.1.11", + "@vitest/browser-webdriverio": "4.1.11", + "@vitest/coverage-istanbul": "4.1.11", + "@vitest/coverage-v8": "4.1.11", + "@vitest/ui": "4.1.11", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -7169,9 +7167,9 @@ } }, "node_modules/yaml": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", "dev": true, "license": "ISC", "optional": true, @@ -7250,9 +7248,9 @@ } }, "node_modules/zod": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.5.4.tgz", - "integrity": "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==", + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz", + "integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index ff397b4..f7a836c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@night-slayer18/leetcode-cli", - "version": "3.5.2", + "version": "3.6.0", "description": "A modern LeetCode CLI built with TypeScript", "type": "module", "main": "dist/index.js", diff --git a/src/__tests__/commands/diff.test.ts b/src/__tests__/commands/diff.test.ts index 3ed0db5..41753b4 100644 --- a/src/__tests__/commands/diff.test.ts +++ b/src/__tests__/commands/diff.test.ts @@ -38,6 +38,7 @@ vi.mock('../../api/client.js', () => ({ vi.mock('../../storage/config.js', () => ({ config: { + getLanguage: vi.fn(() => 'typescript'), getWorkDir: vi.fn(() => '/tmp/leetcode'), }, })); diff --git a/src/__tests__/commands/notes.test.ts b/src/__tests__/commands/notes.test.ts index 4672e8d..14b56a0 100644 --- a/src/__tests__/commands/notes.test.ts +++ b/src/__tests__/commands/notes.test.ts @@ -28,21 +28,26 @@ vi.mock('../../storage/bookmarks.js', () => ({ }, })); -vi.mock('../../api/client.js', () => ({ - leetcodeClient: { - setCredentials: vi.fn(), - checkAuth: vi.fn().mockResolvedValue({ isSignedIn: true, username: 'TestUser' }), - getProblemById: vi.fn().mockResolvedValue({ - questionId: '1', - questionFrontendId: '1', - title: 'Two Sum', - titleSlug: 'two-sum', - difficulty: 'Easy', - status: 'ac', - topicTags: [{ name: 'Array' }], - }), - }, -})); +vi.mock('../../api/client.js', () => { + const problem = { + questionId: '1', + questionFrontendId: '1', + title: 'Two Sum', + titleSlug: 'two-sum', + difficulty: 'Easy', + status: 'ac', + topicTags: [{ name: 'Array' }], + }; + + return { + leetcodeClient: { + setCredentials: vi.fn(), + checkAuth: vi.fn().mockResolvedValue({ isSignedIn: true, username: 'TestUser' }), + getProblemById: vi.fn().mockResolvedValue(problem), + getProblem: vi.fn().mockResolvedValue(problem), + }, + }; +}); vi.mock('fs/promises', () => ({ readFile: vi.fn().mockResolvedValue('# Notes for Two Sum\n\nThis is a test note.'), @@ -71,8 +76,10 @@ vi.mock('ora', () => ({ import { notesCommand } from '../../commands/notes.js'; import { bookmarkCommand } from '../../commands/bookmark.js'; import { bookmarks } from '../../storage/bookmarks.js'; +import { leetcodeClient } from '../../api/client.js'; import { existsSync } from 'fs'; -import { readFile } from 'fs/promises'; +import { readFile, writeFile } from 'fs/promises'; +import { join } from 'path'; describe('Notes and Bookmark Commands', () => { beforeEach(() => { @@ -96,10 +103,51 @@ describe('Notes and Bookmark Commands', () => { expect(openInEditor).toHaveBeenCalled(); }); - it('should handle invalid problem ID', async () => { - await notesCommand('invalid', 'view'); + it('should handle invalid problem ID or name', async () => { + await notesCommand('Two Sum', 'view'); + + expect(outputContains('Invalid problem ID or name')).toBe(true); + expect(leetcodeClient.getProblem).not.toHaveBeenCalled(); + }); + + it('should resolve a problem name to its note file', async () => { + vi.mocked(existsSync).mockReturnValue(true); + + await notesCommand('two-sum', 'view'); + + expect(leetcodeClient.getProblem).toHaveBeenCalledWith('two-sum'); + expect(readFile).toHaveBeenCalledWith(join('/tmp/leetcode', '.notes', '1.md'), 'utf-8'); + }); + + it('should reuse the resolved problem for the note template', async () => { + // Note file does not exist yet, but the notes directory does. + vi.mocked(existsSync).mockImplementation((path) => !String(path).endsWith('1.md')); + + await notesCommand('two-sum', 'edit'); + + expect(leetcodeClient.getProblem).toHaveBeenCalledTimes(1); + expect(leetcodeClient.getProblemById).not.toHaveBeenCalled(); + expect(vi.mocked(writeFile).mock.calls[0][1]).toContain('# 1. Two Sum'); + }); + + it('should report a problem name that does not exist', async () => { + vi.mocked(leetcodeClient.getProblem).mockRejectedValueOnce( + new Error('Invalid input: expected object, received null') + ); + + await notesCommand('not-a-real-problem', 'view'); + + expect(readFile).not.toHaveBeenCalled(); + expect(outputContains('Check the problem name')).toBe(true); + }); + + it('should surface an unexpected lookup failure', async () => { + vi.mocked(leetcodeClient.getProblem).mockRejectedValueOnce(new Error('connect ETIMEDOUT')); + + await notesCommand('two-sum', 'view'); - expect(outputContains('Invalid problem ID')).toBe(true); + expect(readFile).not.toHaveBeenCalled(); + expect(outputContains('connect ETIMEDOUT')).toBe(true); }); }); diff --git a/src/__tests__/commands/reset.test.ts b/src/__tests__/commands/reset.test.ts index e7a0774..7a1c6e2 100644 --- a/src/__tests__/commands/reset.test.ts +++ b/src/__tests__/commands/reset.test.ts @@ -47,6 +47,7 @@ vi.mock('../../storage/config.js', () => ({ workDir: '/tmp/leetcode', site: 'leetcode.com', })), + getLanguage: vi.fn(() => 'typescript'), getWorkDir: vi.fn(() => '/tmp/leetcode'), getSite: vi.fn(() => 'leetcode.com'), }, @@ -133,7 +134,7 @@ describe('resetCommand', () => { expect(result).toBe(true); expect(leetcodeClient.getProblemById).toHaveBeenCalledWith('1'); - expect(findSolutionFile).toHaveBeenCalledWith('/tmp/leetcode', '1'); + expect(findSolutionFile).toHaveBeenCalledWith('/tmp/leetcode', '1', 'typescript'); expect(snapshotStorage.save).toHaveBeenCalledWith( '1', 'Two Sum', @@ -155,6 +156,20 @@ describe('resetCommand', () => { expect(writeFile).toHaveBeenCalled(); }); + it('passes the requested language to the solution file lookup', async () => { + await resetCommand('1', { lang: 'python3' }); + + expect(findSolutionFile).toHaveBeenCalledWith('/tmp/leetcode', '1', 'python3'); + }); + + it('rejects unsupported languages before looking up a file', async () => { + const result = await resetCommand('1', { lang: 'brainfuck' }); + + expect(result).toBe(false); + expect(findSolutionFile).not.toHaveBeenCalled(); + expect(latestSpinner().fail).toHaveBeenCalledWith('Unsupported language: brainfuck'); + }); + it('does not write when no existing solution file is found', async () => { vi.mocked(findSolutionFile).mockResolvedValueOnce(null); diff --git a/src/__tests__/commands/snapshot.test.ts b/src/__tests__/commands/snapshot.test.ts index 83b240c..1713bec 100644 --- a/src/__tests__/commands/snapshot.test.ts +++ b/src/__tests__/commands/snapshot.test.ts @@ -52,6 +52,7 @@ vi.mock('../../storage/snapshots.js', () => ({ vi.mock('../../storage/config.js', () => ({ config: { + getLanguage: vi.fn(() => 'typescript'), getWorkDir: vi.fn(() => '/tmp/leetcode'), }, })); diff --git a/src/__tests__/utils/file-utils.test.ts b/src/__tests__/utils/file-utils.test.ts new file mode 100644 index 0000000..b7c752c --- /dev/null +++ b/src/__tests__/utils/file-utils.test.ts @@ -0,0 +1,77 @@ +import { describe, it, expect, afterEach } from 'vitest'; +import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'fs'; +import { join } from 'path'; +import { tmpdir } from 'os'; +import { findSolutionFile } from '../../utils/fileUtils.js'; + +const tempDirs: string[] = []; + +function createTempWorkspace(): string { + const dir = mkdtempSync(join(tmpdir(), 'leetcode-cli-file-utils-')); + tempDirs.push(dir); + mkdirSync(join(dir, 'Easy', 'Array'), { recursive: true }); + writeFileSync(join(dir, 'Easy', 'Array', '1.two-sum.ts'), '// typescript'); + writeFileSync(join(dir, 'Easy', 'Array', '1.two-sum.py'), '# python'); + writeFileSync(join(dir, 'Easy', 'Array', '1.two-sum.java'), '// java'); + writeFileSync(join(dir, 'Easy', 'Array', '1.two-sum.md'), '# notes'); + return dir; +} + +describe('findSolutionFile', () => { + afterEach(() => { + for (const dir of tempDirs.splice(0)) { + rmSync(dir, { recursive: true, force: true }); + } + }); + + it('finds a solution file without a language filter', async () => { + const dir = createTempWorkspace(); + + const found = await findSolutionFile(dir, '1'); + + expect(found).not.toBeNull(); + expect(found!.endsWith('.md')).toBe(false); + }); + + it('only matches files of the requested language', async () => { + const dir = createTempWorkspace(); + + const found = await findSolutionFile(dir, '1', 'python3'); + + expect(found).not.toBeNull(); + expect(found!.endsWith('.py')).toBe(true); + }); + + it('finds files of the requested language in nested directories', async () => { + const dir = createTempWorkspace(); + + const found = await findSolutionFile(dir, '1', 'java'); + + expect(found).not.toBeNull(); + expect(found!.endsWith('.java')).toBe(true); + }); + + it('returns null when no file matches the requested language', async () => { + const dir = createTempWorkspace(); + + const found = await findSolutionFile(dir, '1', 'cpp'); + + expect(found).toBeNull(); + }); + + it('skips non-code files such as markdown', async () => { + const dir = createTempWorkspace(); + + const found = await findSolutionFile(dir, '1', 'cpp'); + + expect(found).toBeNull(); + }); + + it('returns null for unknown problems', async () => { + const dir = createTempWorkspace(); + + const found = await findSolutionFile(dir, '999'); + + expect(found).toBeNull(); + }); +}); diff --git a/src/__tests__/utils/problem-resolver.test.ts b/src/__tests__/utils/problem-resolver.test.ts new file mode 100644 index 0000000..1e30aaa --- /dev/null +++ b/src/__tests__/utils/problem-resolver.test.ts @@ -0,0 +1,234 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { + isProblemNotFoundError, + resolveProblem, + resolveProblemIdentifier, + type ResolveProblemOptions, +} from '../../utils/problem-resolver.js'; +import { leetcodeClient } from '../../api/client.js'; +import { requireAuth } from '../../utils/auth.js'; +import { mockConsole, outputContains } from '../setup.js'; +import type { ProblemDetail } from '../../types.js'; + +vi.mock('../../api/client.js', () => ({ + leetcodeClient: { + getProblem: vi.fn(), + getProblemById: vi.fn(), + }, +})); + +vi.mock('../../utils/auth.js', () => ({ + requireAuth: vi.fn().mockResolvedValue({ authorized: true }), +})); + +vi.mock('ora', () => { + const spinner = { + start: vi.fn().mockReturnThis(), + stop: vi.fn().mockReturnThis(), + succeed: vi.fn().mockReturnThis(), + fail: vi.fn().mockReturnThis(), + text: '', + }; + return { + default: vi.fn(() => spinner), + }; +}); + +const mockProblemDetail: ProblemDetail = { + questionId: '1', + questionFrontendId: '1', + title: 'Two Sum', + titleSlug: 'two-sum', + difficulty: 'Easy', + content: '

Two Sum

', + hints: [], + isPaidOnly: false, + acRate: 50.1, + topicTags: [{ name: 'Array', slug: 'array' }], + status: null, + codeSnippets: [], + sampleTestCase: '', + exampleTestcases: '', + companyTags: null, + stats: '{}', +}; + +describe('problem-resolver', () => { + beforeEach(() => { + vi.clearAllMocks(); + mockConsole.clear(); + }); + + describe('isProblemNotFoundError', () => { + it('returns true for Zod expected object, received null message', () => { + expect( + isProblemNotFoundError(new Error('Invalid input: expected object, received null')) + ).toBe(true); + }); + + it('returns true for Problem #X not found message', () => { + expect(isProblemNotFoundError(new Error('Problem #42 not found'))).toBe(true); + }); + + it('returns false for other errors', () => { + expect(isProblemNotFoundError(new Error('Network error'))).toBe(false); + expect(isProblemNotFoundError(new Error('Unauthorized'))).toBe(false); + }); + + it('returns false for non-errors', () => { + expect(isProblemNotFoundError('string')).toBe(false); + expect(isProblemNotFoundError(null)).toBe(false); + expect(isProblemNotFoundError(undefined)).toBe(false); + }); + }); + + describe('resolveProblem', () => { + it('rejects invalid problem IDs or slugs without calling API', async () => { + const mockSpinner = { + fail: vi.fn(), + } as unknown as NonNullable; + + const result = await resolveProblem('Invalid Problem Name!', { spinner: mockSpinner }); + + expect(result).toBeNull(); + expect(leetcodeClient.getProblemById).not.toHaveBeenCalled(); + expect(leetcodeClient.getProblem).not.toHaveBeenCalled(); + expect(mockSpinner.fail).toHaveBeenCalledWith( + 'Invalid problem ID or name: Invalid Problem Name!' + ); + }); + + it('fetches problem by numeric ID', async () => { + vi.mocked(leetcodeClient.getProblemById).mockResolvedValueOnce(mockProblemDetail); + + const result = await resolveProblem('1'); + + expect(result).toEqual(mockProblemDetail); + expect(leetcodeClient.getProblemById).toHaveBeenCalledWith('1'); + expect(leetcodeClient.getProblem).not.toHaveBeenCalled(); + }); + + it('fetches problem by title slug', async () => { + vi.mocked(leetcodeClient.getProblem).mockResolvedValueOnce(mockProblemDetail); + + const result = await resolveProblem('two-sum'); + + expect(result).toEqual(mockProblemDetail); + expect(leetcodeClient.getProblem).toHaveBeenCalledWith('two-sum'); + expect(leetcodeClient.getProblemById).not.toHaveBeenCalled(); + }); + + it('handles problem not found when API returns null', async () => { + vi.mocked(leetcodeClient.getProblemById).mockResolvedValueOnce( + null as unknown as Awaited> + ); + const mockSpinner = { + fail: vi.fn(), + } as unknown as NonNullable; + + const result = await resolveProblem('9999', { spinner: mockSpinner }); + + expect(result).toBeNull(); + expect(mockSpinner.fail).toHaveBeenCalledWith('Problem "9999" not found'); + }); + + it('handles unknown slug cleanly without dumping raw Zod error', async () => { + vi.mocked(leetcodeClient.getProblem).mockRejectedValueOnce( + new Error('Invalid input: expected object, received null') + ); + const mockSpinner = { + fail: vi.fn(), + } as unknown as NonNullable; + + const result = await resolveProblem('unknown-slug', { spinner: mockSpinner }); + + expect(result).toBeNull(); + expect(mockSpinner.fail).toHaveBeenCalledWith('Problem "unknown-slug" not found'); + expect(outputContains('Check the problem name')).toBe(true); + expect(outputContains('expected object, received null')).toBe(false); + }); + + it('handles unexpected API errors with failMessage or default', async () => { + vi.mocked(leetcodeClient.getProblemById).mockRejectedValueOnce(new Error('ETIMEDOUT')); + const mockSpinner = { + fail: vi.fn(), + } as unknown as NonNullable; + + const result = await resolveProblem('1', { + spinner: mockSpinner, + failMessage: 'Failed to fetch problem hints', + }); + + expect(result).toBeNull(); + expect(mockSpinner.fail).toHaveBeenCalledWith('Failed to fetch problem hints'); + expect(outputContains('ETIMEDOUT')).toBe(true); + }); + + it('supports silent option to suppress console messages', async () => { + vi.mocked(leetcodeClient.getProblem).mockRejectedValueOnce( + new Error('Invalid input: expected object, received null') + ); + + const result = await resolveProblem('unknown-slug', { silent: true }); + + expect(result).toBeNull(); + expect(outputContains('Check the problem name')).toBe(false); + }); + }); + + describe('resolveProblemIdentifier', () => { + it('returns problemId immediately for numeric ID without calling getProblem', async () => { + const result = await resolveProblemIdentifier('1'); + + expect(result).toEqual({ problemId: '1', problem: null }); + expect(leetcodeClient.getProblem).not.toHaveBeenCalled(); + expect(leetcodeClient.getProblemById).not.toHaveBeenCalled(); + }); + + it('rejects invalid inputs without calling API', async () => { + const result = await resolveProblemIdentifier('invalid problem name!'); + + expect(result).toBeNull(); + expect(outputContains('Invalid problem ID or name')).toBe(true); + expect(leetcodeClient.getProblem).not.toHaveBeenCalled(); + }); + + it('stops if not authorized', async () => { + vi.mocked(requireAuth).mockResolvedValueOnce({ authorized: false }); + + const result = await resolveProblemIdentifier('two-sum'); + + expect(result).toBeNull(); + expect(leetcodeClient.getProblem).not.toHaveBeenCalled(); + }); + + it('resolves slug and returns problemId and problem detail', async () => { + vi.mocked(leetcodeClient.getProblem).mockResolvedValueOnce(mockProblemDetail); + + const result = await resolveProblemIdentifier('two-sum'); + + expect(result).toEqual({ problemId: '1', problem: mockProblemDetail }); + expect(leetcodeClient.getProblem).toHaveBeenCalledWith('two-sum'); + }); + + it('reports not found when slug does not exist', async () => { + vi.mocked(leetcodeClient.getProblem).mockRejectedValueOnce( + new Error('Invalid input: expected object, received null') + ); + + const result = await resolveProblemIdentifier('non-existent-problem'); + + expect(result).toBeNull(); + expect(outputContains('Check the problem name')).toBe(true); + }); + + it('reports unexpected lookup failure', async () => { + vi.mocked(leetcodeClient.getProblem).mockRejectedValueOnce(new Error('connect ECONNREFUSED')); + + const result = await resolveProblemIdentifier('two-sum'); + + expect(result).toBeNull(); + expect(outputContains('connect ECONNREFUSED')).toBe(true); + }); + }); +}); diff --git a/src/commands/collab.ts b/src/commands/collab.ts index d4a9aed..4af7d0e 100644 --- a/src/commands/collab.ts +++ b/src/commands/collab.ts @@ -7,6 +7,7 @@ import { pickCommand } from './pick.js'; import { requireAuth } from '../utils/auth.js'; import { config } from '../storage/config.js'; import { findSolutionFile } from '../utils/fileUtils.js'; +import { resolveLanguageInput } from '../utils/languages.js'; export async function collabHostCommand(problemId: string): Promise { const { authorized, username } = await requireAuth(); @@ -93,7 +94,7 @@ export async function collabJoinCommand(roomCode: string): Promise { } } -export async function collabSyncCommand(): Promise { +export async function collabSyncCommand(options: { lang?: string } = {}): Promise { const session = collabService.getSession(); if (!session) { @@ -107,8 +108,14 @@ export async function collabSyncCommand(): Promise { const spinner = ora('Syncing your code...').start(); try { + const { input, language } = resolveLanguageInput(options.lang); + if (!language) { + spinner.fail(`Unsupported language: ${input}`); + return; + } + const workDir = config.getWorkDir(); - const filePath = await findSolutionFile(workDir, session.problemId); + const filePath = await findSolutionFile(workDir, session.problemId, language); if (!filePath) { spinner.fail(`No solution file found for problem ${session.problemId}`); @@ -132,7 +139,7 @@ export async function collabSyncCommand(): Promise { } } -export async function collabCompareCommand(): Promise { +export async function collabCompareCommand(options: { lang?: string } = {}): Promise { const session = collabService.getSession(); if (!session) { @@ -147,8 +154,14 @@ export async function collabCompareCommand(): Promise { try { // Get my code + const { input, language } = resolveLanguageInput(options.lang); + if (!language) { + spinner.fail(`Unsupported language: ${input}`); + return; + } + const workDir = config.getWorkDir(); - const filePath = await findSolutionFile(workDir, session.problemId); + const filePath = await findSolutionFile(workDir, session.problemId, language); if (!filePath) { spinner.fail(`No solution file found for problem ${session.problemId}`); diff --git a/src/commands/diff.ts b/src/commands/diff.ts index 8d37b82..ff1f806 100644 --- a/src/commands/diff.ts +++ b/src/commands/diff.ts @@ -6,6 +6,7 @@ import { config } from '../storage/config.js'; import { findSolutionFile } from '../utils/fileUtils.js'; import { requireAuth } from '../utils/auth.js'; import { isPathInsideWorkDir } from '../utils/validation.js'; +import { resolveLanguageInput } from '../utils/languages.js'; import { readFile } from 'fs/promises'; import { existsSync } from 'fs'; import { diffLines } from 'diff'; @@ -14,6 +15,7 @@ interface DiffOptions { submission?: string; file?: string; unified?: boolean; + lang?: string; } function showCodeBlock(code: string, label: string): void { @@ -128,7 +130,12 @@ export async function diffCommand(problemId: string, options: DiffOptions): Prom try { // Find current solution file - const filePath = await findSolutionFile(workDir, problemId); + const { input, language } = resolveLanguageInput(options.lang); + if (!language) { + spinner.fail(`Unsupported language: ${input}`); + return; + } + const filePath = await findSolutionFile(workDir, problemId, language); if (!filePath) { spinner.fail(`No solution file found for problem ${problemId}`); diff --git a/src/commands/hint.ts b/src/commands/hint.ts index 9e15826..3a663b5 100644 --- a/src/commands/hint.ts +++ b/src/commands/hint.ts @@ -1,8 +1,8 @@ // Hint command - display problem hints progressively import chalk from 'chalk'; import ora from 'ora'; -import { leetcodeClient } from '../api/client.js'; import { requireAuth } from '../utils/auth.js'; +import { resolveProblem } from '../utils/problem-resolver.js'; import striptags from 'striptags'; export async function hintCommand(idOrSlug: string, options: { all?: boolean }): Promise { @@ -11,50 +11,35 @@ export async function hintCommand(idOrSlug: string, options: { all?: boolean }): const spinner = ora('Fetching problem hints...').start(); - try { - let problem; - - // Check if it's a numeric ID or a title slug - if (/^\d+$/.test(idOrSlug)) { - problem = await leetcodeClient.getProblemById(idOrSlug); - } else { - problem = await leetcodeClient.getProblem(idOrSlug); - } - - if (!problem) { - spinner.fail(`Problem "${idOrSlug}" not found`); - return; - } - - spinner.stop(); - - // Display problem title - console.log(); - console.log(chalk.bold.cyan(`${problem.questionFrontendId}. ${problem.title}`)); - console.log(); - - const hints = problem.hints || []; - - if (hints.length === 0) { - console.log(chalk.yellow('⚠ No hints available for this problem.')); - console.log(chalk.gray('Try working through the problem description and examples first!')); - return; - } - - if (options.all) { - // Show all hints at once - hints.forEach((hint, index) => { - displayHint(hint, index + 1, hints.length); - }); - } else { - // Show hints one at a time with prompts - await showHintsProgressively(hints); - } - } catch (error) { - spinner.fail('Failed to fetch problem hints'); - if (error instanceof Error) { - console.log(chalk.red(error.message)); - } + const problem = await resolveProblem(idOrSlug, { + spinner, + failMessage: 'Failed to fetch problem hints', + }); + if (!problem) return; + + spinner.stop(); + + // Display problem title + console.log(); + console.log(chalk.bold.cyan(`${problem.questionFrontendId}. ${problem.title}`)); + console.log(); + + const hints = problem.hints || []; + + if (hints.length === 0) { + console.log(chalk.yellow('⚠ No hints available for this problem.')); + console.log(chalk.gray('Try working through the problem description and examples first!')); + return; + } + + if (options.all) { + // Show all hints at once + hints.forEach((hint, index) => { + displayHint(hint, index + 1, hints.length); + }); + } else { + // Show hints one at a time with prompts + await showHintsProgressively(hints); } } diff --git a/src/commands/notes.ts b/src/commands/notes.ts index 3a5502b..56c2fa7 100644 --- a/src/commands/notes.ts +++ b/src/commands/notes.ts @@ -7,23 +7,20 @@ import { config } from '../storage/config.js'; import { requireAuth } from '../utils/auth.js'; import { openInEditor } from '../utils/editor.js'; import { leetcodeClient } from '../api/client.js'; -import { isProblemId } from '../utils/validation.js'; +import { resolveProblemIdentifier } from '../utils/problem-resolver.js'; +import type { ProblemDetail } from '../types.js'; type NoteAction = 'view' | 'edit'; export async function notesCommand( - problemId: string, + idOrName: string, action?: string, options: { silent?: boolean } = {} ): Promise { - if (!isProblemId(problemId)) { - if (!options.silent) { - console.log(chalk.red(`Invalid problem ID: ${problemId}`)); - console.log(chalk.gray('Problem ID must be a positive integer')); - } - return; - } + const resolved = await resolveProblemIdentifier(idOrName, options); + if (!resolved) return; + const { problemId, problem } = resolved; const noteAction: NoteAction = action === 'view' ? 'view' : 'edit'; const notesDir = join(config.getWorkDir(), '.notes'); @@ -36,7 +33,7 @@ export async function notesCommand( if (noteAction === 'view') { await viewNote(notePath, problemId); } else { - await editNote(notePath, problemId, options); + await editNote(notePath, problemId, problem, options); } } @@ -65,10 +62,11 @@ async function viewNote(notePath: string, problemId: string): Promise { async function editNote( notePath: string, problemId: string, + problem: ProblemDetail | null, options: { silent?: boolean } = {} ): Promise { if (!existsSync(notePath)) { - const template = await generateNoteTemplate(problemId); + const template = await generateNoteTemplate(problemId, problem); await writeFile(notePath, template, 'utf-8'); if (!options.silent) console.log(chalk.green(`✓ Created notes file for problem ${problemId}`)); } @@ -77,13 +75,16 @@ async function editNote( await openInEditor(notePath); } -async function generateNoteTemplate(problemId: string): Promise { +async function generateNoteTemplate( + problemId: string, + fetchedProblem: ProblemDetail | null = null +): Promise { let header = `# Problem ${problemId} Notes\n\n`; - const { authorized } = await requireAuth(); + const { authorized } = fetchedProblem ? { authorized: true } : await requireAuth(); if (authorized) { try { - const problem = await leetcodeClient.getProblemById(problemId); + const problem = fetchedProblem ?? (await leetcodeClient.getProblemById(problemId)); if (problem) { header = `# ${problemId}. ${problem.title}\n\n`; header += `**Difficulty:** ${problem.difficulty}\n`; diff --git a/src/commands/pick.ts b/src/commands/pick.ts index 31d06c9..4cb6477 100644 --- a/src/commands/pick.ts +++ b/src/commands/pick.ts @@ -4,7 +4,6 @@ import { existsSync } from 'fs'; import { join } from 'path'; import ora from 'ora'; import chalk from 'chalk'; -import { leetcodeClient } from '../api/client.js'; import { config } from '../storage/config.js'; import { getCodeTemplate, @@ -15,6 +14,7 @@ import { import { openInEditor } from '../utils/editor.js'; import { requireAuth } from '../utils/auth.js'; import { normalizeLanguageInput } from '../utils/languages.js'; +import { resolveProblem } from '../utils/problem-resolver.js'; export interface PickOptions { lang?: string; @@ -27,21 +27,10 @@ export async function pickCommand(idOrSlug: string, options: PickOptions): Promi const spinner = ora({ text: 'Fetching problem details...', spinner: 'dots' }).start(); - try { - let problem; - - // Check if it's a numeric ID or a title slug - if (/^\d+$/.test(idOrSlug)) { - problem = await leetcodeClient.getProblemById(idOrSlug); - } else { - problem = await leetcodeClient.getProblem(idOrSlug); - } - - if (!problem) { - spinner.fail(`Problem "${idOrSlug}" not found`); - return false; - } + const problem = await resolveProblem(idOrSlug, { spinner }); + if (!problem) return false; + try { spinner.text = 'Generating solution file...'; // Determine language diff --git a/src/commands/reset.ts b/src/commands/reset.ts index 24d828c..9da5729 100644 --- a/src/commands/reset.ts +++ b/src/commands/reset.ts @@ -3,37 +3,40 @@ import { readFile, realpath, writeFile } from 'fs/promises'; import { basename, extname } from 'path'; import ora from 'ora'; import chalk from 'chalk'; -import { leetcodeClient } from '../api/client.js'; import { requireAuth } from '../utils/auth.js'; import { config } from '../storage/config.js'; import { snapshotStorage } from '../storage/snapshots.js'; import { findSolutionFile, detectLanguageFromFile } from '../utils/fileUtils.js'; import { generateSolutionFile, getPremiumPlaceholderCode } from '../utils/templates.js'; import { isPathInsideWorkDir } from '../utils/validation.js'; -import { resolveSupportedLanguageFromLeetCodeSlug } from '../utils/languages.js'; - -function isProblemNotFoundError(error: unknown): boolean { - if (!(error instanceof Error)) return false; - - return ( - error.message.includes('expected object, received null') || - /^Problem #.+ not found$/.test(error.message) - ); +import { + resolveLanguageInput, + resolveSupportedLanguageFromLeetCodeSlug, +} from '../utils/languages.js'; +import { resolveProblem } from '../utils/problem-resolver.js'; + +interface ResetOptions { + lang?: string; } -export async function resetCommand(idOrSlug: string): Promise { +export async function resetCommand(idOrSlug: string, options: ResetOptions = {}): Promise { const { authorized } = await requireAuth(); if (!authorized) return false; const spinner = ora({ text: 'Fetching problem details...', spinner: 'dots' }).start(); + const problem = await resolveProblem(idOrSlug, { spinner }); + if (!problem) return false; + try { - const problem = /^\d+$/.test(idOrSlug) - ? await leetcodeClient.getProblemById(idOrSlug) - : await leetcodeClient.getProblem(idOrSlug); + const { input, language: lookupLang } = resolveLanguageInput(options.lang); + if (!lookupLang) { + spinner.fail(`Unsupported language: ${input}`); + return false; + } const workDir = config.getWorkDir(); - const filePath = await findSolutionFile(workDir, problem.questionFrontendId); + const filePath = await findSolutionFile(workDir, problem.questionFrontendId, lookupLang); if (!filePath) { spinner.fail(`No solution file found for problem ${problem.questionFrontendId}`); @@ -113,11 +116,6 @@ export async function resetCommand(idOrSlug: string): Promise { console.log(chalk.gray(`Backup: ${backup.name}`)); return true; } catch (error) { - if (isProblemNotFoundError(error)) { - spinner.fail(`Problem "${idOrSlug}" not found`); - return false; - } - spinner.fail('Failed to reset solution'); if (error instanceof Error) { console.log(chalk.red(error.message)); diff --git a/src/commands/show.ts b/src/commands/show.ts index a9a442d..0d01b02 100644 --- a/src/commands/show.ts +++ b/src/commands/show.ts @@ -1,9 +1,8 @@ // Show command - display problem description import ora from 'ora'; -import chalk from 'chalk'; -import { leetcodeClient } from '../api/client.js'; import { requireAuth } from '../utils/auth.js'; import { displayProblemDetail } from '../utils/display.js'; +import { resolveProblem } from '../utils/problem-resolver.js'; export async function showCommand(idOrSlug: string): Promise { const { authorized } = await requireAuth(); @@ -11,27 +10,9 @@ export async function showCommand(idOrSlug: string): Promise { const spinner = ora('Fetching problem...').start(); - try { - let problem; + const problem = await resolveProblem(idOrSlug, { spinner }); + if (!problem) return; - // Check if it's a numeric ID or a title slug - if (/^\d+$/.test(idOrSlug)) { - problem = await leetcodeClient.getProblemById(idOrSlug); - } else { - problem = await leetcodeClient.getProblem(idOrSlug); - } - - if (!problem) { - spinner.fail(`Problem "${idOrSlug}" not found`); - return; - } - - spinner.stop(); - displayProblemDetail(problem); - } catch (error) { - spinner.fail('Failed to fetch problem'); - if (error instanceof Error) { - console.log(chalk.red(error.message)); - } - } + spinner.stop(); + displayProblemDetail(problem); } diff --git a/src/commands/snapshot.ts b/src/commands/snapshot.ts index a7ae66d..24e5283 100644 --- a/src/commands/snapshot.ts +++ b/src/commands/snapshot.ts @@ -6,6 +6,7 @@ import { detectLanguageFromFile, findSolutionFile } from '../utils/fileUtils.js' import { readFile, writeFile } from 'fs/promises'; import { extname, basename } from 'path'; import { diffLines } from 'diff'; +import { resolveLanguageInput } from '../utils/languages.js'; function formatTimeAgo(dateStr: string): string { const date = new Date(dateStr); @@ -21,12 +22,22 @@ function formatTimeAgo(dateStr: string): string { return `${diffDays}d ago`; } -export async function snapshotSaveCommand(problemId: string, name?: string): Promise { +export async function snapshotSaveCommand( + problemId: string, + name?: string, + options: { lang?: string } = {} +): Promise { const workDir = config.getWorkDir(); try { + const { input, language } = resolveLanguageInput(options.lang); + if (!language) { + console.log(chalk.red(`Unsupported language: ${input}`)); + return; + } + // Find the solution file - const filePath = await findSolutionFile(workDir, problemId); + const filePath = await findSolutionFile(workDir, problemId, language); if (!filePath) { console.log(chalk.red(`No solution file found for problem ${problemId}`)); @@ -105,7 +116,11 @@ export async function snapshotListCommand(problemId: string): Promise { console.log(chalk.gray(` delete: leetcode snapshot delete ${problemId} `)); } -export async function snapshotRestoreCommand(problemId: string, idOrName: string): Promise { +export async function snapshotRestoreCommand( + problemId: string, + idOrName: string, + options: { lang?: string } = {} +): Promise { const workDir = config.getWorkDir(); try { @@ -120,8 +135,14 @@ export async function snapshotRestoreCommand(problemId: string, idOrName: string return; } + const { input, language } = resolveLanguageInput(options.lang); + if (!language) { + console.log(chalk.red(`Unsupported language: ${input}`)); + return; + } + // Find the solution file - const filePath = await findSolutionFile(workDir, problemId); + const filePath = await findSolutionFile(workDir, problemId, language); if (!filePath) { console.log(chalk.red(`No solution file found for problem ${problemId}`)); diff --git a/src/commands/submissions.ts b/src/commands/submissions.ts index 9c62a0a..eb7bdea 100644 --- a/src/commands/submissions.ts +++ b/src/commands/submissions.ts @@ -8,6 +8,7 @@ import { requireAuth } from '../utils/auth.js'; import { config } from '../storage/config.js'; import { displaySubmissionsList } from '../utils/display.js'; import { LANG_SLUG_MAP, LANGUAGE_EXTENSIONS } from '../utils/templates.js'; +import { resolveProblem } from '../utils/problem-resolver.js'; import type { SupportedLanguage } from '../types.js'; interface SubmissionsOptions { @@ -25,19 +26,13 @@ export async function submissionsCommand( const spinner = ora('Fetching problem info...').start(); - try { - let problem; - if (/^\d+$/.test(idOrSlug)) { - problem = await leetcodeClient.getProblemById(idOrSlug); - } else { - problem = await leetcodeClient.getProblem(idOrSlug); - } - - if (!problem) { - spinner.fail(`Problem "${idOrSlug}" not found`); - return; - } + const problem = await resolveProblem(idOrSlug, { + spinner, + failMessage: 'Failed to fetch problem info', + }); + if (!problem) return; + try { const slug = problem.titleSlug; spinner.text = 'Fetching submissions...'; diff --git a/src/commands/submit.ts b/src/commands/submit.ts index eeed74f..a1cb79f 100644 --- a/src/commands/submit.ts +++ b/src/commands/submit.ts @@ -12,12 +12,17 @@ import { displaySubmissionResult } from '../utils/display.js'; import { maybeShowStarPrompt } from '../utils/star-prompt.js'; import { findSolutionFile, findFileByName, getLangSlugFromExtension } from '../utils/fileUtils.js'; import { isProblemId, isFileName, isPathInsideWorkDir } from '../utils/validation.js'; +import { resolveLanguageInput } from '../utils/languages.js'; + +interface SubmitOptions { + lang?: string; +} /** * Submit command - submit solution to LeetCode * @param fileOrId - Problem ID, filename, or file path */ -export async function submitCommand(fileOrId: string): Promise { +export async function submitCommand(fileOrId: string, options: SubmitOptions = {}): Promise { const { authorized } = await requireAuth(); if (!authorized) return; @@ -27,7 +32,12 @@ export async function submitCommand(fileOrId: string): Promise { // Resolve file path based on input type if (isProblemId(fileOrId)) { // Input is a problem ID - find by ID - const found = await findSolutionFile(workDir, fileOrId); + const { input, language } = resolveLanguageInput(options.lang); + if (!language) { + console.log(chalk.red(`Unsupported language: ${input}`)); + return; + } + const found = await findSolutionFile(workDir, fileOrId, language); if (!found) { console.log(chalk.red(`No solution file found for problem ${fileOrId}`)); console.log(chalk.gray(`Looking in: ${workDir}`)); diff --git a/src/commands/test.ts b/src/commands/test.ts index af942e0..931f568 100644 --- a/src/commands/test.ts +++ b/src/commands/test.ts @@ -10,10 +10,12 @@ import { config } from '../storage/config.js'; import { displayTestResult } from '../utils/display.js'; import { findSolutionFile, findFileByName, getLangSlugFromExtension } from '../utils/fileUtils.js'; import { isProblemId, isFileName, isPathInsideWorkDir } from '../utils/validation.js'; +import { resolveLanguageInput } from '../utils/languages.js'; interface TestOptions { testcase?: string; visualize?: boolean; + lang?: string; } export async function testCommand(fileOrId: string, options: TestOptions): Promise { @@ -24,7 +26,12 @@ export async function testCommand(fileOrId: string, options: TestOptions): Promi const workDir = config.getWorkDir(); if (isProblemId(fileOrId)) { - const found = await findSolutionFile(workDir, fileOrId); + const { input, language } = resolveLanguageInput(options.lang); + if (!language) { + console.log(chalk.red(`Unsupported language: ${input}`)); + return; + } + const found = await findSolutionFile(workDir, fileOrId, language); if (!found) { console.log(chalk.red(`No solution file found for problem ${fileOrId}`)); console.log(chalk.gray(`Looking in: ${workDir}`)); diff --git a/src/commands/timer.ts b/src/commands/timer.ts index 8ccc95e..e0db625 100644 --- a/src/commands/timer.ts +++ b/src/commands/timer.ts @@ -1,10 +1,10 @@ // Timer command - Interview mode with countdown import ora from 'ora'; import chalk from 'chalk'; -import { leetcodeClient } from '../api/client.js'; import { timerStorage } from '../storage/timer.js'; import { pickCommand } from './pick.js'; import { requireAuth } from '../utils/auth.js'; +import { resolveProblem } from '../utils/problem-resolver.js'; const DEFAULT_TIMES: Record = { Easy: 20, @@ -76,21 +76,12 @@ export async function timerCommand( const spinner = ora('Fetching problem...').start(); - try { - let problem; - if (/^\d+$/.test(idOrSlug)) { - problem = await leetcodeClient.getProblemById(idOrSlug); - } else { - problem = await leetcodeClient.getProblem(idOrSlug); - } - - if (!problem) { - spinner.fail(`Problem "${idOrSlug}" not found`); - return; - } + const problem = await resolveProblem(idOrSlug, { spinner }); + if (!problem) return; - spinner.stop(); + spinner.stop(); + try { // Determine timer duration const durationMinutes = options.minutes ?? DEFAULT_TIMES[problem.difficulty] ?? 30; diff --git a/src/index.ts b/src/index.ts index 5776bbe..922770b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -71,7 +71,7 @@ program .name('leetcode') .usage('[command] [options]') .description(chalk.bold.cyan('🔥 A modern LeetCode CLI built with TypeScript')) - .version('3.5.2', '-v, --version', 'Output the version number') + .version('3.6.0', '-v, --version', 'Output the version number') .helpOption('-h, --help', 'Display help for command') .addHelpText( 'after', @@ -266,6 +266,7 @@ program .description('Test solution against sample test cases') .option('-c, --testcase ', 'Custom test case') .option('-V, --visualize', 'Visual output for data structures (arrays, trees, etc.)') + .option('-l, --lang ', 'Language to filter solution lookup by problem ID') .addHelpText( 'after', ` @@ -285,6 +286,7 @@ ${chalk.gray('Testcases use \\n to separate multiple inputs.')} program .command('reset ') .description('Reset solution file to the original LeetCode stub') + .option('-l, --lang ', 'Language to filter solution lookup') .addHelpText( 'after', ` @@ -295,14 +297,15 @@ ${chalk.yellow('Examples:')} ${chalk.gray('Overwrites the existing local solution file immediately.')} ` ) - .action(async (id) => { - await resetCommand(id); + .action(async (id, options) => { + await resetCommand(id, options); }); program .command('submit ') .alias('x') .description('Submit solution to LeetCode') + .option('-l, --lang ', 'Language to filter solution lookup by problem ID') .addHelpText( 'after', ` @@ -321,6 +324,7 @@ program .option('-s, --submission ', 'Compare with specific submission ID') .option('-f, --file ', 'Compare with a local file') .option('-u, --unified', 'Show unified diff (line-by-line changes)') + .option('-l, --lang ', 'Language to filter solution lookup') .addHelpText( 'after', ` @@ -414,7 +418,7 @@ ${chalk.yellow('Examples:')} .action(bookmarkCommand); program - .command('note [action]') + .command('note [action]') .description('View or edit notes for a problem') .addHelpText( 'after', @@ -425,8 +429,11 @@ ${chalk.yellow('Actions:')} ${chalk.yellow('Examples:')} ${chalk.cyan('$ leetcode note 1')} Edit notes for problem 1 + ${chalk.cyan('$ leetcode note two-sum')} Edit notes by problem name ${chalk.cyan('$ leetcode note 1 edit')} Edit notes (explicit) - ${chalk.cyan('$ leetcode note 1 view')} View notes in terminal + ${chalk.cyan('$ leetcode note two-sum view')} View notes in terminal + +${chalk.gray('Problem names resolve to the same note file as the problem ID.')} ` ) .action(notesCommand); @@ -559,11 +566,16 @@ collabCmd .description('Join a collaboration session') .action(collabJoinCommand); -collabCmd.command('sync').description('Sync your code with partner').action(collabSyncCommand); +collabCmd + .command('sync') + .description('Sync your code with partner') + .option('-l, --lang ', 'Language to filter solution lookup') + .action(collabSyncCommand); collabCmd .command('compare') .description('Compare your solution with partner') + .option('-l, --lang ', 'Language to filter solution lookup') .action(collabCompareCommand); collabCmd @@ -598,6 +610,7 @@ ${chalk.yellow('Examples:')} snapshotCmd .command('save [name]') .description('Save current solution as a snapshot') + .option('-l, --lang ', 'Language to filter solution lookup') .action(snapshotSaveCommand); snapshotCmd @@ -608,6 +621,7 @@ snapshotCmd snapshotCmd .command('restore ') .description('Restore a snapshot') + .option('-l, --lang ', 'Language to filter solution lookup') .action(snapshotRestoreCommand); snapshotCmd diff --git a/src/tui/lib/layout.ts b/src/tui/lib/layout.ts index 73bc8f1..4d1ec6f 100644 --- a/src/tui/lib/layout.ts +++ b/src/tui/lib/layout.ts @@ -20,7 +20,7 @@ export function truncate(str: string, maxLen: number): string { let result = ''; let visible = 0; - for (let i = 0; i < str.length && visible < target; ) { + for (let i = 0; i < str.length && visible < target;) { if (str[i] === '\x1b') { const match = str.slice(i).match(/^\x1B\[[0-9;]*[a-zA-Z]/); if (match) { diff --git a/src/utils/fileUtils.ts b/src/utils/fileUtils.ts index 5639e0e..c20575b 100644 --- a/src/utils/fileUtils.ts +++ b/src/utils/fileUtils.ts @@ -12,6 +12,7 @@ const MAX_SEARCH_DEPTH = 5; export async function findSolutionFile( dir: string, problemId: string, + language?: SupportedLanguage | null, currentDepth: number = 0 ): Promise { if (!existsSync(dir)) return null; @@ -25,12 +26,13 @@ export async function findSolutionFile( const fullPath = join(dir, entry.name); if (entry.isDirectory()) { - const found = await findSolutionFile(fullPath, problemId, currentDepth + 1); + const found = await findSolutionFile(fullPath, problemId, language, currentDepth + 1); if (found) return found; } else if (entry.name.startsWith(`${problemId}.`)) { // Check it's a valid code file, not a markdown or other file const ext = entry.name.split('.').pop()?.toLowerCase(); if (ext && ext in EXT_TO_LANG_MAP) { + if (language && EXT_TO_LANG_MAP[ext] !== language) continue; return fullPath; } } diff --git a/src/utils/languages.ts b/src/utils/languages.ts index 1cca6b7..934676b 100644 --- a/src/utils/languages.ts +++ b/src/utils/languages.ts @@ -1,4 +1,5 @@ import type { CodeSnippet, SupportedLanguage } from '../types.js'; +import { config } from '../storage/config.js'; export const SUPPORTED_LANGUAGES: SupportedLanguage[] = [ 'typescript', @@ -83,6 +84,14 @@ export function normalizeLanguageInput(input: string): SupportedLanguage | null return LANGUAGE_ALIAS_MAP[normalizeKey(input)] ?? null; } +export function resolveLanguageInput(langInput?: string): { + input: string; + language: SupportedLanguage | null; +} { + const input = langInput ?? config.getLanguage(); + return { input, language: normalizeLanguageInput(input) }; +} + export function resolveSupportedLanguageFromLeetCodeSlug(slug: string): SupportedLanguage | null { return normalizeLanguageInput(slug); } diff --git a/src/utils/problem-resolver.ts b/src/utils/problem-resolver.ts new file mode 100644 index 0000000..a3e7e0c --- /dev/null +++ b/src/utils/problem-resolver.ts @@ -0,0 +1,150 @@ +import chalk from 'chalk'; +import ora, { type Ora } from 'ora'; +import { leetcodeClient } from '../api/client.js'; +import { requireAuth } from './auth.js'; +import { isProblemId, isTitleSlug } from './validation.js'; +import type { ProblemDetail } from '../types.js'; + +export interface ResolveProblemOptions { + spinner?: Ora | null; + silent?: boolean; + failMessage?: string; +} + +export interface ResolvedProblemIdentifier { + problemId: string; + problem: ProblemDetail | null; +} + +/** + * Checks whether an error represents a "problem not found" response. + * Handles both GraphQL null responses (Zod validation failure) and + * ID lookup errors ("Problem #X not found"). + */ +export function isProblemNotFoundError(error: unknown): boolean { + if (!(error instanceof Error)) return false; + + return ( + error.message.includes('expected object, received null') || + /^Problem #.+ not found$/i.test(error.message) + ); +} + +/** + * Validates and resolves a problem by either numeric ID or title slug, + * returning the full ProblemDetail. Catches Zod null-response errors cleanly + * and prevents dumping raw Zod validation errors to stdout. + */ +export async function resolveProblem( + idOrSlug: string, + options: ResolveProblemOptions = {} +): Promise { + const slug = idOrSlug.trim(); + const isId = isProblemId(slug); + const isSlug = isTitleSlug(slug); + + if (!isId && !isSlug) { + if (options.spinner) { + options.spinner.fail(`Invalid problem ID or name: ${idOrSlug}`); + } else if (!options.silent) { + console.log(chalk.red(`Invalid problem ID or name: ${idOrSlug}`)); + } + if (!options.silent) { + console.log(chalk.gray('Use a problem ID (e.g. 1) or a problem name (e.g. two-sum)')); + } + return null; + } + + try { + const problem = isId + ? await leetcodeClient.getProblemById(slug) + : await leetcodeClient.getProblem(slug); + + if (!problem) { + if (options.spinner) { + options.spinner.fail(`Problem "${idOrSlug}" not found`); + } else if (!options.silent) { + console.log(chalk.red(`Problem "${idOrSlug}" not found`)); + } + return null; + } + + return problem; + } catch (error) { + const notFound = isProblemNotFoundError(error); + const message = error instanceof Error ? error.message : ''; + + if (options.spinner) { + options.spinner.fail( + notFound + ? `Problem "${idOrSlug}" not found` + : (options.failMessage ?? 'Failed to fetch problem') + ); + } else if (!options.silent && notFound) { + console.log(chalk.red(`Problem "${idOrSlug}" not found`)); + } + + if (!options.silent) { + if (notFound) { + if (isSlug) { + console.log(chalk.gray('Check the problem name, or use the problem ID instead.')); + } + } else if (message) { + console.log(chalk.red(message)); + } + } + + return null; + } +} + +/** + * Resolves a problem identifier for commands (like `note`) that primarily need + * the numeric ID, deferring full problem fetching when an ID is already supplied. + * If a slug is supplied, fetches the problem and returns both the numeric ID and detail. + */ +export async function resolveProblemIdentifier( + idOrName: string, + options: { silent?: boolean } = {} +): Promise { + const slug = idOrName.trim(); + + if (isProblemId(slug)) { + return { problemId: slug, problem: null }; + } + + if (!isTitleSlug(slug)) { + if (!options.silent) { + console.log(chalk.red(`Invalid problem ID or name: ${idOrName}`)); + console.log(chalk.gray('Use a problem ID (e.g. 1) or a problem name (e.g. two-sum)')); + } + return null; + } + + const { authorized } = await requireAuth(); + if (!authorized) return null; + + const spinner = options.silent + ? null + : ora({ text: `Looking up "${slug}"...`, spinner: 'dots' }).start(); + + try { + const problem = await leetcodeClient.getProblem(slug); + spinner?.succeed(`Found ${problem.questionFrontendId}. ${problem.title}`); + return { problemId: problem.questionFrontendId, problem }; + } catch (error) { + const message = error instanceof Error ? error.message : ''; + const notFound = isProblemNotFoundError(error); + + spinner?.fail(notFound ? `Problem "${slug}" not found` : `Failed to look up "${slug}"`); + + if (!options.silent) { + if (notFound) { + console.log(chalk.gray('Check the problem name, or use the problem ID instead.')); + } else if (message) { + console.log(chalk.red(message)); + } + } + return null; + } +} diff --git a/src/utils/validation.ts b/src/utils/validation.ts index 2af7d32..efe346d 100644 --- a/src/utils/validation.ts +++ b/src/utils/validation.ts @@ -9,6 +9,15 @@ export function isFileName(input: string): boolean { return !input.includes('/') && !input.includes('\\') && input.includes('.'); } +const TITLE_SLUG_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/; + +/** + * A LeetCode problem name in title-slug form, e.g. "two-sum". + */ +export function isTitleSlug(input: string): boolean { + return TITLE_SLUG_REGEX.test(input.trim()); +} + const WORKSPACE_NAME_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/; const SNAPSHOT_NAME_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9 _-]{0,63}$/;